C# Understanding ComputeData() and SolveInstance() -- what happens when?

Dear all,

I’m trying to run a computationally extensive operation on a background thread from within a custom component.

Currently, I’m using the C# async/await syntax.
Inside the SolveInstance() I’m starting a task on another thread like so:

protected override async void SolveInstance(IGH_DataAccess DA)
{
    	// Only start new run if we're not running already...
      	 if (!Working)
      	{
      		// Set busy flag.
      		Working = true;

  		// Call the background worker.
  		// The worker will start working on it's own thread.
  		// The await keyword waits for worker to complete and *returns controll to 
  		// the caller of SolveInstance*. That's why the GUI is still responsive.
  		List<object> result = await doWork(options["Duration"].Value);

  		// Once the worker has returned, we continue here.
  		// The returned list contains the actual return value and a success bool
  		// which indicates if the worker ran successfully (true) or was cancelled (false).
  		if ((bool)result[result.Count-1])
  		{
  			MessageBox.Show("Done! Result: " + (int)result[0]);
  			// Set outputs.
  			DA.SetData("Number1", (int)result[0]);
  		}
  		else if (!(bool)result[result.Count - 1])
  		{
  			MessageBox.Show("Cancelled.");
  		}
  		Working = false;
  		}
  	// If this is running already, consider killing the worker and restarting it.
  	else
  	{
  		;//TODO
  	}
  }

The way this works is (as I hope to have understood): the doWork method gets called (it’s an async method as well and starts the background thread) and then – before the rest of SolveInstance is run – control is handed back to whoever called SolveInstance. This way, the GUI stays responsive which is what I need to show a progress bar on my component as well as handle mouse events on the component’s Cancel button. Once the doWork method has finished, control jumps to the rest of SolveInstance.

background
(Sorry for the missing mouse cursor…)

The problem, however, is that the outputs do not get set properly. While doWork is still running, they’re not set (which is OK for starters) but once the work is done, they are set to an additional branch and also not propagated to the next component. I think the problem is that ComputeData() – which is what calls SolveInstance() if I’m not mistaken – sets the component’s Phase property to Computed when SolveInstance() returns just after starting doWork(). Then, once doWork() is completed, the rest of SolveInstance() is run but the component itself has finished its usual stuff long ago.

To get this to work, I would need a better understanding of the single steps that are carried out during the solution of a component.

For example, I noticed that ComputeData() is called multiple times upon changing an input. The number of ComputeData() calls seems to somehow depend on the number of inputs but I could not get a clear understanding of it.

I have the feeling that it would be better to start doWork() from ComputeData() and only run SolveInstance() once that is done to set the outputs. If anyone could therefore shed some light into the ComputeData() method I’d be really grateful!

Sorry for the lengthly post btw…
Kind regards,
Paul

3 Likes

I think I had some similar issues a while ago, don’t know if it helps…

It seems to me that you maybe could write your output in the SolveInstance() and then just schedule a new solution, as long es everything else your component does happens outside the SolveInstance().

Anyhow, just on a conceptual level: If your canvas stays responsive for a cancel button click, it also means the document is responsive and the user can do all kinds of stuff (change your component inputs, retrigger new solutions, delete your component) which could maybe cause all kinds of weird stuff.

Hi Paul,

I forgot I was supposed to respond to this, sorry.

ComputeData is part of all active objects in Grasshopper that participate in solutions. ClearData, CollectData, ComputeData is sort of their public api for wiping stale data and generating new data.

The GH_Component base class creates an iterator object inside ComputeData which calls the SolveInstance() method repeatedly, each time with different combinations of input ranges. ComputeData is the bit that makes sure that each required set of input values is handled once. It’s complicated because it has to take into account the optionality of inputs, as well as their access levels (item, list, tree).

If you want to just speed up a calculation using threading, then we have a new mechanism in GH for Rhino6 that helps with that.

However it looks as though you want to calculate stuff in the background for a long time while yielding control back to the UI in the meantime.

I think the following steps are required:

  1. Override BeforeSolveInstance (which is called only once per solution) and cancel any running tasks. Then move your component into a state where it can begin queuing tasks.
  2. Inside SolveInstance queue all the tasks you want to start off with, but probably do not start them quite yet. After you’re done queuing, set blank data to the outputs (or, if you’re willing to write extra code, assign the old data again).
  3. Inside AfterSolveInstance or perhaps in response to the GH_Document.SolutionEnd event, begin running your tasks.
  4. When all your tasks are complete and they haven’t been interrupted by a new solution (step 1), then trigger a new solution by calling this.ExpireSolution(true)*. Your component must be smart enough to distinguish between solutions where it’s supposed to cancel it’s tasks, and those where it’s supposed to harvest the finished data and assign it to outputs.

* do not call that method from a non-UI thread. If you’re not sure whether you’re on a UI thread or not, then you must either Invoke on the UI thread or call GH_Document.ScheduleSolution(), which is threadsafe.

8 Likes