fix(data matching): corrects cancellation on fast input changes & other things:

corrects getdata for child tasks, syncs current state, extra signalling for SetData phase
This commit is contained in:
Dimitrie Stefanescu
2020-10-07 17:02:36 +01:00
parent e5ba777fd2
commit fd229e1a7f
4 changed files with 77 additions and 38 deletions
@@ -8,6 +8,9 @@ using Timer = System.Timers.Timer;
namespace GrasshopperAsyncComponent
{
/// <summary>
/// Inherit your component from this class to make all the async goodness available.
/// </summary>
public abstract class GH_AsyncComponent : GH_Component
{
public override Guid ComponentGuid { get => new Guid("5DBBD498-0326-4E25-83A5-424D8DC493D4"); }
@@ -30,12 +33,23 @@ namespace GrasshopperAsyncComponent
int Iterations = 0;
public WorkerInstance BaseWorker { get; set; }
bool SetData = false;
List<WorkerInstance> Workers;
List<Task> Tasks;
List<CancellationTokenSource> CancelationSources;
/// <summary>
/// Set this property inside the constructor of your derived component.
/// </summary>
public WorkerInstance BaseWorker { get; set; }
/// <summary>
/// Optional: if you have opinions on how the default system task scheduler should treat your workers, set it here.
/// </summary>
public TaskCreationOptions? TaskCreationOptions { get; set; } = null;
protected GH_AsyncComponent(string name, string nickname, string description, string category, string subCategory) : base(name, nickname, description, category, subCategory)
{
@@ -61,8 +75,10 @@ namespace GrasshopperAsyncComponent
{
State++;
if (State == Iterations)
if (State == Workers.Count)
{
SetData = true;
Workers.Reverse();
Rhino.RhinoApp.InvokeOnUiThread((Action)delegate
{
ExpireSolution(true);
@@ -74,28 +90,35 @@ namespace GrasshopperAsyncComponent
Workers = new List<WorkerInstance>();
CancelationSources = new List<CancellationTokenSource>();
Tasks = new List<Task>();
}
protected override void BeforeSolveInstance()
{
if (State != 0) return;
if (State != 0 && SetData) return;
foreach (var source in CancelationSources) source.Cancel();
CancelationSources.Clear();
Workers.Clear();
Errors.Clear();
Tasks.Clear();
State = 0;
Iterations = 0;
base.BeforeSolveInstance();
}
protected override void AfterSolveInstance()
{
if (State == 0 && Tasks.Count > 0)
{
foreach (var task in Tasks) task.Start();
}
}
protected override void SolveInstance(IGH_DataAccess DA)
{
if (State == 0)
{
Iterations++;
if (BaseWorker == null)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Worker class not provided.");
@@ -116,32 +139,50 @@ namespace GrasshopperAsyncComponent
var tokenSource = new CancellationTokenSource();
CurrentWorker.CancellationToken = tokenSource.Token;
CurrentWorker.Id = DA.Iteration.ToString();
var CurrentRun = new Task(() => CurrentWorker.DoWork(ReportProgress, ReportError, Done), tokenSource.Token, TaskCreationOptions.LongRunning);
Task CurrentRun;
if (TaskCreationOptions != null)
{
CurrentRun = new Task(() => CurrentWorker.DoWork(ReportProgress, ReportError, Done), tokenSource.Token, (TaskCreationOptions)TaskCreationOptions);
}
else
{
CurrentRun = new Task(() => CurrentWorker.DoWork(ReportProgress, ReportError, Done), tokenSource.Token);
}
// Add cancelation source to our bag
CancelationSources.Add(tokenSource);
// Add the worker to our list
Workers.Add(CurrentWorker);
CurrentRun.Start();
Tasks.Add(CurrentRun);
return;
}
var test = DA.Iteration;
Workers[DA.Iteration].SetData(DA);
// Note we're decrementing the state here.
if (--State == 0)
if (SetData)
{
foreach (var (message, type) in Errors)
{
AddRuntimeMessage(type, message);
}
if (Workers.Count > 0)
Workers[--State].SetData(DA);
Message = "Done";
OnDisplayExpired(true);
Errors.Clear();
if (State == 0)
{
foreach (var (message, type) in Errors)
{
AddRuntimeMessage(type, message);
}
CancelationSources.Clear();
Workers.Clear();
Errors.Clear();
Tasks.Clear();
SetData = false;
Message = "Done";
//OnDisplayExpired(true);
OnDisplayExpired(false);
}
}
}
}
@@ -32,7 +32,7 @@ namespace GrasshopperAsyncComponent
/// <summary>
/// This method is where the actual calculation/computation/heavy lifting should be done.
/// <b>Make sure you always check as frequently as you can if <see cref="WorkerInstance.CancellationToken"/> is cancelled. For an example, see the <see cref="GrasshopperAsyncComponent.SampleImplementations.PrimeCalculator"/>.</b>
/// <b>Make sure you always check as frequently as you can if <see cref="WorkerInstance.CancellationToken"/> is cancelled. For an example, see the <see cref="GrasshopperAsyncComponent.SampleImplementations.PrimeCalculatorWorker"/>.</b>
/// </summary>
/// <param name="ReportProgress">Call this to report progress up to the parent component.</param>
/// <param name="ReportError">Call this to report errors up to the parent component.</param>
@@ -63,7 +63,7 @@
<Compile Include="Info\GrasshopperAsyncComponentInfo.cs" />
<Compile Include="Base\IAsyncComponentWorker.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SampleImplementations\SampleAsyncComponent.cs" />
<Compile Include="SampleImplementations\Sample_PrimeCalculatorAsyncComponent.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
@@ -8,7 +8,7 @@ using System.Threading.Tasks;
namespace GrasshopperAsyncComponent.SampleImplementations
{
public class SampleAsyncComponent : GH_AsyncComponent
public class Sample_PrimeCalculatorAsyncComponent : GH_AsyncComponent
{
public override Guid ComponentGuid { get => new Guid("DF2B93E2-052D-4BE4-BC62-90DC1F169BF6"); }
@@ -16,19 +16,19 @@ namespace GrasshopperAsyncComponent.SampleImplementations
public override GH_Exposure Exposure => GH_Exposure.primary;
public SampleAsyncComponent() : base("Sample Async Component", "CYCLOMAXOTRON", "Meaningless labour.", "Samples", "Async")
public Sample_PrimeCalculatorAsyncComponent() : base("Sample Async Component", "PRIME", "Calculates the nth prime number.", "Samples", "Async")
{
BaseWorker = new PrimeCalculator();
BaseWorker = new PrimeCalculatorWorker();
}
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddIntegerParameter("Max iterations", "M", "How many useless cycles should we spin. Minimum 10, maximum 1000.", GH_ParamAccess.item);
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.AddTextParameter("Output", "O", "Will just say hello world after spinning.", GH_ParamAccess.item);
pManager.AddTextParameter("Output", "O", "The n-th prime number.", GH_ParamAccess.item);
}
}
@@ -75,7 +75,7 @@ namespace GrasshopperAsyncComponent.SampleImplementations
}
}
public class PrimeCalculator : WorkerInstance
public class PrimeCalculatorWorker : WorkerInstance
{
int TehNthPrime { get; set; } = 100;
long ThePrime { get; set; } = -1;
@@ -120,24 +120,22 @@ namespace GrasshopperAsyncComponent.SampleImplementations
Done();
}
public override WorkerInstance Duplicate() => new PrimeCalculator();
public override WorkerInstance Duplicate() => new PrimeCalculatorWorker();
public override void GetData(IGH_DataAccess DA, GH_ComponentParamServer Params)
{
if (CancellationToken.IsCancellationRequested) return;
int _nthPrime = 100;
DA.GetData(0, ref _nthPrime);
if (_nthPrime > 1000000) _nthPrime = 1000000;
if (_nthPrime < 1) _nthPrime = 1;
int _maxIterations = 100;
DA.GetData(0, ref _maxIterations);
if (_maxIterations > 1000000) _maxIterations = 1000000;
if (_maxIterations < 10) _maxIterations = 10;
TehNthPrime = _maxIterations;
TehNthPrime = _nthPrime;
}
public override void SetData(IGH_DataAccess DA)
{
if (CancellationToken.IsCancellationRequested) return;
DA.SetData(0, $"Hello world. Worker {Id} has found for that the {TehNthPrime}th prime is: {ThePrime}");
DA.SetData(0, $"W_ID {Id}: {TehNthPrime}th prime is: {ThePrime}");
}
}