diff --git a/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs b/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs index 569a7a6..f955ce9 100644 --- a/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs +++ b/GrasshopperAsyncComponent/Base/GH_AsyncComponent.cs @@ -8,6 +8,9 @@ using Timer = System.Timers.Timer; namespace GrasshopperAsyncComponent { + /// + /// Inherit your component from this class to make all the async goodness available. + /// 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 Workers; + List Tasks; + List CancelationSources; + /// + /// Set this property inside the constructor of your derived component. + /// + public WorkerInstance BaseWorker { get; set; } + + /// + /// Optional: if you have opinions on how the default system task scheduler should treat your workers, set it here. + /// + 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(); CancelationSources = new List(); + Tasks = new List(); } 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); + } } } } diff --git a/GrasshopperAsyncComponent/Base/IAsyncComponentWorker.cs b/GrasshopperAsyncComponent/Base/IAsyncComponentWorker.cs index a470595..b0943c0 100644 --- a/GrasshopperAsyncComponent/Base/IAsyncComponentWorker.cs +++ b/GrasshopperAsyncComponent/Base/IAsyncComponentWorker.cs @@ -32,7 +32,7 @@ namespace GrasshopperAsyncComponent /// /// This method is where the actual calculation/computation/heavy lifting should be done. - /// Make sure you always check as frequently as you can if is cancelled. For an example, see the . + /// Make sure you always check as frequently as you can if is cancelled. For an example, see the . /// /// Call this to report progress up to the parent component. /// Call this to report errors up to the parent component. diff --git a/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj b/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj index a981acc..8a12863 100644 --- a/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj +++ b/GrasshopperAsyncComponent/GrasshopperAsyncComponent.csproj @@ -63,7 +63,7 @@ - + diff --git a/GrasshopperAsyncComponent/SampleImplementations/SampleAsyncComponent.cs b/GrasshopperAsyncComponent/SampleImplementations/Sample_PrimeCalculatorAsyncComponent.cs similarity index 77% rename from GrasshopperAsyncComponent/SampleImplementations/SampleAsyncComponent.cs rename to GrasshopperAsyncComponent/SampleImplementations/Sample_PrimeCalculatorAsyncComponent.cs index 05e7703..35b1479 100644 --- a/GrasshopperAsyncComponent/SampleImplementations/SampleAsyncComponent.cs +++ b/GrasshopperAsyncComponent/SampleImplementations/Sample_PrimeCalculatorAsyncComponent.cs @@ -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}"); } }