Hi, well, kind of
Sorry if I was not clear enough, but I got to the threading part,the main problem is how to make sure, that the component also waits for the background thread to finish, before reporting itself as solved.
My problem is about how to handle the output parameters, and the components downstream while we give control back from SolveInstance()
I have attached a simplistic test component, where I tried various methods.
Currently, I use a Task to run in the background, which seems good in simple cases, but trips up when chaining these components. (see at bottom for the custom component code)
As long as it is only one component, or parallel components, it seems to work alright.
The component gives back control to the UI, and after the computing task is finished in the background, updates it’s outputs with the results, and updates any other components downstream. So far so good.
But as soon as I try to chain these type of components together, the ones downstream lock up the canvas, and fail to give out a runtime message about background computing. (Eventually they too reach the correct output.)
GHLibraryTest.gha (15 KB)
async_test.gh (9.5 KB)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using Grasshopper.Kernel;
namespace GHLibraryTest
{
public class AdvancedParametersExample : GH_Component
{
public AdvancedParametersExample() : base("Trigonometric values", "TrigVal", "Computes sine, socine and tan values", "Extra", "Test")
{
}
public override Guid ComponentGuid
{
get
{
return new Guid("2757f7fb-68e2-4986-809a-aa251e286570");
}
}
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddNumberParameter("Angle", "A", "The angle to measure", GH_ParamAccess.item);
pManager.AddBooleanParameter("Radians", "R", "Work in radians", GH_ParamAccess.item, true); // default, we assume radians.
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddNumberParameter("Sin", "sin", "The sine of the angle", GH_ParamAccess.item);
pManager.AddNumberParameter("Cos", "cos", "The cosine of the angle", GH_ParamAccess.item);
pManager.AddNumberParameter("Tan", "tan", "The tangent of the angle", GH_ParamAccess.item);
}
protected override void SolveInstance(IGH_DataAccess DA)
{
if (skip)
{
DA.SetData(0, sin);
DA.SetData(1, cos);
DA.SetData(2, tan);
skip = false;
return;
}
// Define variables to hold inputs, with initial values
double angle = double.NaN;
bool radians = true;
// Collect input parameters, abort if unsuccesfull
if (!DA.GetData(0, ref angle))
{
return;
}
if (!DA.GetData(1, ref radians))
{
return;
}
if (!Rhino.RhinoMath.IsValidDouble(angle))
{
return;
}
// Convert to degrees if needed
if (radians == false)
{
angle = Rhino.RhinoMath.ToRadians(angle);
}
this.angle = angle;
this.DA = DA;
DA.DisableGapLogic(); // required to prevent a tree output, with nulls on one branch, and the outputs filled in by bg process on another branch NOTE: propably should be only set in a case where we are sure to run the bg process.
Task computeTask = new Task(calculate);
computeTask.ContinueWith(task =>
{
if (task.Status == TaskStatus.RanToCompletion)
{
skip = true;
ExpireSolution(true);
}
else
{
Grasshopper.Instances.RedrawAll();
}
},
TaskScheduler.FromCurrentSynchronizationContext());
computeTask.Start();
Grasshopper.Instances.RedrawAll();
}
private double angle;
private double sin;
private double cos;
private double tan;
private bool skip;
private IGH_DataAccess DA;
private void calculate()
{
this.AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, "background work...");
sin = Math.Sin(angle);
Thread.Sleep(5000);
cos = Math.Cos(angle);
Thread.Sleep(5000);
tan = Math.Tan(angle);
// Produce output values
DA.SetData(0, sin);
DA.SetData(1, cos);
DA.SetData(2, tan);
skip = true;
ExpireDownStreamObjects();
ClearRuntimeMessages();
}
}
}