In a Grasshopper component that we are working on, we have a separate thread running (using a Task), that periodically triggers the component to recalculate the solution by calling ExpireSolution() wrapped within InvokeOnUiThread. We have overridden the GH_Component.RemovedFromDocument() method, such that we can gracefully terminate the thread (using a CancellationTokenSource).
Now the problem that we are facing, is that once we close Grasshopper and subsequently close Rhino, we get a NullReferenceException while calling ExpireSolution(). This occurs at the moment that Grasshopper asks the user whether he wants to save the project or not. In that case the ui completely freezes (which makes sense as the ui thread is being blocked by the call to ExpireSolution).
I haven’t found a way to detect that the user is trying to close Rhino, such that I could terminate the thread and not run into this situation.
Also, I’ve tried to use InvokeAndWait() but this also didn’t do the trick.
I’ve attached a minimal example (FailingComponent.cs (1.8 KB)) that reproduces the problem. Simply drag the “FailingComponent” onto the Grasshopper canvas, and try to close Rhino. It will result in the NullReferenceException.
My questions are:
Is the way how I’m dealing with threads/Tasks correct, or is there a better way to accomplish what I want?
Is the way how I’m trying to trigger a recomputation of the solution correct, or is there a better way?
The NullReferenceException that I get, is that a programming mistake on my side, or perhaps a bug in Rhino or Grasshopper?
I can’t get it to hang in my debugger, but I think the callback you’re looking for is
public override void DocumentContextChanged(GH_Document document, GH_DocumentContext context)
{
if (context == GH_DocumentContext.Unloaded)
{
_source?.Cancel();
_source = null;
}
if (context == GH_DocumentContext.Loaded)
if (_source is null)
_source = new CancellationTokenSource();
}
There are additional context changed calls for Close and Open, but Loaded and Unloaded are always part of the call sequence in the case of a document being opened for the first time, being closed from the UI, being made an inactive document (i.e. another document becomes loaded), being made active, and Rhino shutdown.
ps. I was reminded of Failing by your example code. I always listen to it whenever I feel my job is difficult…
That’s a nice video that indeed pretty much sums up how I’m feeling in solving this issue
I’ve tried your solution, but it seems that the event is triggered after the crash/exception.
I’ve attached a short debug video in which you can clearly see what I’m doing and what led to the crash. Maybe that will help. I’ve also attached a new version of the code (FailingComponent.cs (2.6 KB)) that was used in the video. You might need to set a breakpoint at the place where it fails. The first time it stops there, and then you can remove the breakpoint. Afterwards, it might fail there. It is a race condition, so you might have to try a couple of times. But by setting a breakpoint first, I had quite consistent behavior, in that it always crashed while closing Rhino.
Instead of using the DocumentContextChanged callback that you suggested, which in my case is triggered after the crash, I managed to use the RhinoDoc.CloseDocument callback. This one seems to be triggered before the crash, in which case I can cancel the task, as well as any already-queued function on the ui thread.
I’ve attached the working code, in case you’re interested.