Hey @mjuliani! They’re very similar overall, and I think porting over could be quite easy. The GH_AsyncComponent is actually using tasks to delegate its work - it spins them up for you. In the example above, each component iteration has its own independent task in which the n-th prime is calculated.
I think the simplest analogy is that the DoWork
function is the equivalent of the SolveResults
method from McNeel’s TaskCapableComponent tutorial.
That said, there’s been some changes in the last week that solved the flash of null data that was plaguing this approach, and we’ve switched from an interface implementation to an abstract class. I’m breaking down below an example implementation (taken from here).
First off, the actual component implementation:
public class Sample_PrimeCalculatorAsyncComponent : GH_AsyncComponent
{
public override Guid ComponentGuid { get => new Guid("22C612B0-2C57-47CE-B9FE-E10621F18933"); }
protected override System.Drawing.Bitmap Icon { get => null; }
public override GH_Exposure Exposure => GH_Exposure.primary;
public Sample_PrimeCalculatorAsyncComponent() : base("The N-th Prime Calculator", "PRIME", "Calculates the nth prime number.", "Samples", "Async")
{
// 👉 Important! Set the BaseWorker prop of this component to whatever your "worker" class implementation is called.
BaseWorker = new PrimeCalculatorWorker();
}
// Input output params: proceed as usual!
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddIntegerParameter("N", "N", "Which n-th prime number. Minimum 1, maximum one million. Take care, it can burn your CPU.", GH_ParamAccess.item);
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddNumberParameter("Output", "O", "The n-th prime number.", GH_ParamAccess.item);
}
}
Things to note:
- Your component class needs to inherit from the
GH_AsyncComponent
class.
- Define your input and output parameters as always.
- In the constructor you need to instantiate your actual worker class that basically does the heavy lifting. More on this below!
-
Do not override the solve instance method. This is taken care of by the parent!
GH_AsyncComponent
class. Your actual computation logic will go in the DoWork
method of the WorkerInstance class. More below!
Here’s a sample implementation of a worker class. This class is the key!
// Note: we've moved away from an interface to an abstract class. Anyway:
public class PrimeCalculatorWorker : WorkerInstance
{
// You can hold whatever copies of local state you need to in here. These can be set from
// the component above, or from the GetData function below.
int TheNthPrime { get; set; } = 100;
long ThePrime { get; set; } = -1;
// This function will be called at the beginning, and in it you can just collect your input data
// just like you would in a normal component. Store whatever you need inside this class so
// you can use them later in the `DoWork` function.
public override void GetData(IGH_DataAccess DA, GH_ComponentParamServer Params)
{
int _nthPrime = 100;
DA.GetData(0, ref _nthPrime);
if (_nthPrime > 1000000) _nthPrime = 1000000;
if (_nthPrime < 1) _nthPrime = 1;
TheNthPrime = _nthPrime;
}
// In here simply set data like you normally would in a grasshopper component.
public override void SetData(IGH_DataAccess DA)
{
// 👉 Checking for cancellation!
if (CancellationToken.IsCancellationRequested) return;
DA.SetData(0, ThePrime);
}
// This is where the magic happens! Whatever logic you have should happen in here.
// Make sure to check for task cancellation often and rigorously!
public override void DoWork(Action<string, double> ReportProgress, Action<string, GH_RuntimeMessageLevel> ReportError, Action Done)
{
// 👉 Checking for cancellation!
if (CancellationToken.IsCancellationRequested) return;
int count = 0;
long a = 2;
while (count < TheNthPrime)
{
// 👉 Checking for cancellation!
if (CancellationToken.IsCancellationRequested) return;
// more calc code... (see repo for full implementation)
// Call this action to report your calculation progress. It expects values from 0 to 1 (percentages).
ReportProgress(Id, ((double)count) / TheNthPrime);
if (prime > 0) count++;
a++;
}
ThePrime = --a;
// When you're done, simply call the `Done` action provided. This will tell the parent component that it's time to start setting data once all other workers finish.
Done();
}
// Last but not least, you need to proved a way to duplicate this class. This is important
// if you pass in state from the parent component. In this case, we don't, so we just
// return a new instance of it.
public override WorkerInstance Duplicate() => new PrimeCalculatorWorker();
}
Okay, a little summary of the WorkerInstance implementation:
-
DoWork
- this is where your calculation logic goes. It will run on in its own Task. Inside DoWork you can
- call
ReportProgress
to report upwards this specific instance’s progress.
- call
Done
at the end of your calculations.
-
check for task cancellation! If you don’t, nothing will happen, but you’ll keep the computer busy for no reason at all.
-
SetData
& GetData
- just like a normal Grasshopper component.
-
Duplicate
- returns a fresh instance of the class, with all its needed state passed on from the parent component. Just return a new instance if that’s not the case.
Anyways, hope this helps! Let us know if you have any other questions, or if you find bugs.