Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a53cef31a8 | |||
| 8dc8c9a0fb | |||
| 65fab58006 | |||
| b93aa1a76a | |||
| bdc105fa95 | |||
| 59f87b2a7d | |||
| c314fc5834 | |||
| b7e57b1ec8 | |||
| 0f649d6d2d | |||
| 92f814dab4 |
@@ -16,7 +16,7 @@ jobs:
|
||||
command: msbuild GrasshopperAsyncComponent.sln /p:Configuration=Release
|
||||
- run:
|
||||
name: Pack NuGet
|
||||
command: cd GrasshopperAsyncComponent; nuget pack GrasshopperAsyncComponent.csproj -Prop Configuration=Release
|
||||
command: cd GrasshopperAsyncComponent; nuget pack GrasshopperAsyncComponent.csproj -Prop Configuration=Release -Symbols -SymbolPackageFormat snupkg
|
||||
- run:
|
||||
name: Push NuGet
|
||||
command: cd GrasshopperAsyncComponent; nuget push *.nupkg -Source https://api.nuget.org/v3/index.json -ApiKey $env:NUGET_AUTH_TOKEN -SkipDuplicate
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using System;
|
||||
using Grasshopper.Kernel;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Grasshopper.Kernel;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace GrasshopperAsyncComponent
|
||||
@@ -21,14 +22,14 @@ namespace GrasshopperAsyncComponent
|
||||
Action<string, double> ReportProgress;
|
||||
|
||||
public ConcurrentDictionary<string, double> ProgressReports;
|
||||
|
||||
|
||||
Action Done;
|
||||
|
||||
Timer DisplayProgressTimer;
|
||||
|
||||
int State = 0;
|
||||
|
||||
bool SetData = false;
|
||||
int SetData = 0;
|
||||
|
||||
public List<WorkerInstance> Workers;
|
||||
|
||||
@@ -55,16 +56,19 @@ namespace GrasshopperAsyncComponent
|
||||
ReportProgress = (id, value) =>
|
||||
{
|
||||
ProgressReports[id] = value;
|
||||
if (!DisplayProgressTimer.Enabled) DisplayProgressTimer.Start();
|
||||
if (!DisplayProgressTimer.Enabled)
|
||||
{
|
||||
DisplayProgressTimer.Start();
|
||||
}
|
||||
};
|
||||
|
||||
Done = () =>
|
||||
{
|
||||
State++;
|
||||
|
||||
if (State == Workers.Count)
|
||||
Interlocked.Increment(ref State);
|
||||
if (State == Workers.Count && SetData == 0)
|
||||
{
|
||||
SetData = true;
|
||||
Interlocked.Exchange(ref SetData, 1);
|
||||
|
||||
// We need to reverse the workers list to set the outputs in the same order as the inputs.
|
||||
Workers.Reverse();
|
||||
|
||||
@@ -82,9 +86,31 @@ namespace GrasshopperAsyncComponent
|
||||
Tasks = new List<Task>();
|
||||
}
|
||||
|
||||
public void RequestCancellation()
|
||||
{
|
||||
foreach(var token in CancellationSources)
|
||||
{
|
||||
token.Cancel();
|
||||
}
|
||||
|
||||
CancellationSources.Clear();
|
||||
Workers.Clear();
|
||||
ProgressReports.Clear();
|
||||
Tasks.Clear();
|
||||
|
||||
Interlocked.Exchange(ref SetData, 0);
|
||||
|
||||
Message = "Done";
|
||||
OnDisplayExpired(true);
|
||||
}
|
||||
|
||||
public virtual void DisplayProgress(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
if (Workers.Count == 0) return;
|
||||
if (Workers.Count == 0 || ProgressReports.Values.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Workers.Count == 1)
|
||||
{
|
||||
Message = ProgressReports.Values.Last().ToString("0.00%");
|
||||
@@ -108,39 +134,52 @@ namespace GrasshopperAsyncComponent
|
||||
|
||||
protected override void BeforeSolveInstance()
|
||||
{
|
||||
if (State != 0 && SetData) return;
|
||||
if (State != 0 && SetData == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var source in CancellationSources) source.Cancel();
|
||||
Debug.WriteLine("Killing");
|
||||
|
||||
foreach (var source in CancellationSources)
|
||||
{
|
||||
source.Cancel();
|
||||
}
|
||||
|
||||
CancellationSources.Clear();
|
||||
Workers.Clear();
|
||||
ProgressReports.Clear();
|
||||
Tasks.Clear();
|
||||
|
||||
State = 0;
|
||||
|
||||
//var test = Params.Output[0].VolatileData;
|
||||
Interlocked.Exchange(ref State, 0);
|
||||
}
|
||||
|
||||
protected override void AfterSolveInstance()
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("After solve instance was called " + State + " ? " + Workers.Count);
|
||||
// We need to start all the tasks as close as possible to each other.
|
||||
if (State == 0 && Tasks.Count > 0)
|
||||
if (State == 0 && Tasks.Count > 0 && SetData == 0)
|
||||
{
|
||||
foreach (var task in Tasks) task.Start();
|
||||
//var test = Params.Output[0].VolatileData;
|
||||
System.Diagnostics.Debug.WriteLine("After solve INVOKATIONM");
|
||||
foreach (var task in Tasks)
|
||||
{
|
||||
task.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ExpireDownStreamObjects()
|
||||
{
|
||||
// Prevents the flash of null data until the new solution is ready
|
||||
if (SetData)
|
||||
if (SetData == 1)
|
||||
{
|
||||
base.ExpireDownStreamObjects();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SolveInstance(IGH_DataAccess DA)
|
||||
{
|
||||
//return;
|
||||
if (State == 0)
|
||||
{
|
||||
if (BaseWorker == null)
|
||||
@@ -164,13 +203,13 @@ namespace GrasshopperAsyncComponent
|
||||
currentWorker.CancellationToken = tokenSource.Token;
|
||||
currentWorker.Id = $"Worker-{DA.Iteration}";
|
||||
|
||||
var currentRun = TaskCreationOptions != null
|
||||
? new Task(() => currentWorker.DoWork(ReportProgress, Done), tokenSource.Token, (TaskCreationOptions)TaskCreationOptions)
|
||||
var currentRun = TaskCreationOptions != null
|
||||
? new Task(() => currentWorker.DoWork(ReportProgress, Done), tokenSource.Token, (TaskCreationOptions)TaskCreationOptions)
|
||||
: new Task(() => currentWorker.DoWork(ReportProgress, Done), tokenSource.Token);
|
||||
|
||||
|
||||
// Add cancellation source to our bag
|
||||
CancellationSources.Add(tokenSource);
|
||||
|
||||
|
||||
// Add the worker to our list
|
||||
Workers.Add(currentWorker);
|
||||
|
||||
@@ -179,22 +218,50 @@ namespace GrasshopperAsyncComponent
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SetData) return;
|
||||
|
||||
if (Workers.Count > 0)
|
||||
Workers[--State].SetData(DA);
|
||||
if (SetData == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Workers.Count > 0)
|
||||
{
|
||||
Interlocked.Decrement(ref State);
|
||||
Workers[State].SetData(DA);
|
||||
}
|
||||
|
||||
if (State != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (State != 0) return;
|
||||
|
||||
CancellationSources.Clear();
|
||||
Workers.Clear();
|
||||
ProgressReports.Clear();
|
||||
Tasks.Clear();
|
||||
|
||||
SetData = false;
|
||||
Interlocked.Exchange(ref SetData, 0);
|
||||
|
||||
Message = "Done";
|
||||
OnDisplayExpired(true);
|
||||
}
|
||||
|
||||
public void RequestCancellation()
|
||||
{
|
||||
foreach (var source in CancellationSources)
|
||||
{
|
||||
source.Cancel();
|
||||
}
|
||||
|
||||
CancellationSources.Clear();
|
||||
Workers.Clear();
|
||||
ProgressReports.Clear();
|
||||
Tasks.Clear();
|
||||
|
||||
Interlocked.Exchange(ref State, 0);
|
||||
Interlocked.Exchange(ref SetData, 0);
|
||||
Message = "Cancelled";
|
||||
OnDisplayExpired(true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@@ -45,10 +49,12 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grasshopper">
|
||||
<Version>6.29.20238.11501</Version>
|
||||
<Version>6.33.20343.16431</Version>
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="RhinoCommon">
|
||||
<Version>6.29.20238.11501</Version>
|
||||
<Version>6.33.20343.16431</Version>
|
||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
<package >
|
||||
<metadata>
|
||||
<id>GrasshopperAsyncComponent</id>
|
||||
<version>0.2.0.0</version>
|
||||
<version>0.2.1.0</version>
|
||||
<title>GrasshopperAsyncComponent</title>
|
||||
<authors>Speckle Systems</authors>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">Apache-2.0</license>
|
||||
<projectUrl>https://github.com/specklesystems/GrasshopperAsyncComponent</projectUrl>
|
||||
<iconUrl>http://icon_url_here_or_delete_this_line/</iconUrl>
|
||||
<description>Jankless Grasshopper Component</description>
|
||||
<releaseNotes>Initial release.</releaseNotes>
|
||||
<releaseNotes>Hotfix for race condition preventing full component cycle.</releaseNotes>
|
||||
<copyright>Speckle Systems</copyright>
|
||||
<tags>grasshopper rhino mcneel gh_component</tags>
|
||||
</metadata>
|
||||
|
||||
@@ -55,8 +55,8 @@
|
||||
<Compile Include="SampleImplementations\Sample_UslessCyclesComponent.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grasshopper" Version="6.29.20238.11501" />
|
||||
<PackageReference Include="RhinoCommon" Version="6.29.20238.11501" />
|
||||
<PackageReference Include="Grasshopper" Version="6.33.20343.16431" />
|
||||
<PackageReference Include="RhinoCommon" Version="6.33.20343.16431" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GrasshopperAsyncComponent\GrasshopperAsyncComponent.csproj">
|
||||
|
||||
+22
-4
@@ -6,6 +6,7 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GrasshopperAsyncComponent;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace GrasshopperAsyncComponentDemo.SampleImplementations
|
||||
{
|
||||
@@ -30,7 +31,24 @@ namespace GrasshopperAsyncComponentDemo.SampleImplementations
|
||||
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
|
||||
{
|
||||
pManager.AddNumberParameter("Output", "O", "The n-th prime number.", GH_ParamAccess.item);
|
||||
}
|
||||
|
||||
protected override void AppendAdditionalComponentMenuItems(ToolStripDropDown menu)
|
||||
{
|
||||
base.AppendAdditionalComponentMenuItems(menu);
|
||||
Menu_AppendItem(menu, "Cancel", (s, e) =>
|
||||
{
|
||||
this.RequestCancellation();
|
||||
});
|
||||
}
|
||||
|
||||
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
|
||||
{
|
||||
base.AppendAdditionalMenuItems(menu);
|
||||
Menu_AppendItem(menu, "Cancel", (s, e) =>
|
||||
{
|
||||
RequestCancellation();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +62,7 @@ namespace GrasshopperAsyncComponentDemo.SampleImplementations
|
||||
public override void DoWork(Action<string, double> ReportProgress, Action Done)
|
||||
{
|
||||
// 👉 Checking for cancellation!
|
||||
if (CancellationToken.IsCancellationRequested) return;
|
||||
if (CancellationToken.IsCancellationRequested) { Done(); return; }
|
||||
|
||||
int count = 0;
|
||||
long a = 2;
|
||||
@@ -53,14 +71,14 @@ namespace GrasshopperAsyncComponentDemo.SampleImplementations
|
||||
while (count < TheNthPrime)
|
||||
{
|
||||
// 👉 Checking for cancellation!
|
||||
if (CancellationToken.IsCancellationRequested) return;
|
||||
if (CancellationToken.IsCancellationRequested) { Done(); return; }
|
||||
|
||||
long b = 2;
|
||||
int prime = 1;// to check if found a prime
|
||||
while (b * b <= a)
|
||||
{
|
||||
// 👉 Checking for cancellation!
|
||||
if (CancellationToken.IsCancellationRequested) return;
|
||||
if (CancellationToken.IsCancellationRequested) { Done(); return; }
|
||||
|
||||
if (a % b == 0)
|
||||
{
|
||||
@@ -98,7 +116,7 @@ namespace GrasshopperAsyncComponentDemo.SampleImplementations
|
||||
public override void SetData(IGH_DataAccess DA)
|
||||
{
|
||||
// 👉 Checking for cancellation!
|
||||
if (CancellationToken.IsCancellationRequested) return;
|
||||
if (CancellationToken.IsCancellationRequested) { return; }
|
||||
|
||||
DA.SetData(0, ThePrime);
|
||||
}
|
||||
|
||||
+12
-2
@@ -6,6 +6,7 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GrasshopperAsyncComponent;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace GrasshopperAsyncComponentDemo.SampleImplementations
|
||||
{
|
||||
@@ -31,6 +32,15 @@ namespace GrasshopperAsyncComponentDemo.SampleImplementations
|
||||
{
|
||||
pManager.AddTextParameter("Output", "O", "Nothing really interesting.", GH_ParamAccess.item);
|
||||
}
|
||||
|
||||
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
|
||||
{
|
||||
base.AppendAdditionalMenuItems(menu);
|
||||
Menu_AppendItem(menu, "Cancel", (s, e) =>
|
||||
{
|
||||
RequestCancellation();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class UselessCyclesWorker : WorkerInstance
|
||||
@@ -42,7 +52,7 @@ namespace GrasshopperAsyncComponentDemo.SampleImplementations
|
||||
public override void DoWork(Action<string, double> ReportProgress, Action Done)
|
||||
{
|
||||
// Checking for cancellation
|
||||
if (CancellationToken.IsCancellationRequested) return;
|
||||
if (CancellationToken.IsCancellationRequested) { Done(); return; }
|
||||
|
||||
for (int i = 0; i <= MaxIterations; i++)
|
||||
{
|
||||
@@ -53,7 +63,7 @@ namespace GrasshopperAsyncComponentDemo.SampleImplementations
|
||||
ReportProgress(Id, ((double)(i + 1) / (double)MaxIterations));
|
||||
|
||||
// Checking for cancellation
|
||||
if (CancellationToken.IsCancellationRequested) return;
|
||||
if (CancellationToken.IsCancellationRequested) { Done(); return; }
|
||||
}
|
||||
|
||||
Done();
|
||||
|
||||
@@ -15,6 +15,7 @@ Looks nice, doesn't it? Notice that the solution runs "eagerly" - every time the
|
||||
|
||||
- **Grasshopper and Rhino are still responsive!**
|
||||
- **There's progress reporting!** (personally I hate waiting for Gh to unfreeze...).
|
||||
- **Thread safe**: 99% of the times this won't explode in your face. It still might though!
|
||||
|
||||
### Approach
|
||||
|
||||
@@ -46,6 +47,25 @@ Q: Does this component use all my cores? A: OH YES. It goes WROOOM.
|
||||
|
||||

|
||||
|
||||
Q: Can I enable cancellation of a longer running task?
|
||||
|
||||
A: Yes, now you can! In your component, just add a right click menu action like so:
|
||||
|
||||
```cs
|
||||
|
||||
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
|
||||
{
|
||||
base.AppendAdditionalMenuItems(menu);
|
||||
Menu_AppendItem(menu, "Cancel", (s, e) =>
|
||||
{
|
||||
RequestCancellation();
|
||||
});
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Debugging
|
||||
|
||||
Quite easy:
|
||||
|
||||
Reference in New Issue
Block a user