18 Commits

Author SHA1 Message Date
Alan Rynne 66a1ccb8ba fix: Bump version 2021-06-11 10:17:38 +02:00
Alan Rynne 4bb2759be9 Merge pull request #16 from specklesystems/alan/rhino-version-downgrade
Downgraded rhino nugets to 6.28 for broader compatibility
2021-06-09 16:55:47 +02:00
Alan Rynne 0a1a3e1627 Downgraded rhino nugets to 6.28 for broader compatibility 2021-06-09 16:53:51 +02:00
Dimitrie Stefanescu 57c7dd0bd0 Merge branch 'main' of https://github.com/specklesystems/GrasshopperAsyncComponent into main 2021-04-03 19:01:24 +01:00
Dimitrie Stefanescu 330a55b058 fix(nuspec): versioning 2021-04-03 19:01:04 +01:00
Dimitrie Stefanescu 1b92138ce9 Update README.md
closes #7
2021-04-03 18:39:23 +01:00
Dimitrie Stefanescu 2f2be53bff fix: fixes incorrect cancellation 2021-04-03 18:22:22 +01:00
Dimitrie Stefanescu af6b381578 fix(merge conflicts): fixed merge conflicts (for real) 2021-04-03 18:17:22 +01:00
Dimitrie Stefanescu a53cef31a8 Merge pull request #13 from specklesystems/dim/fixes
Dim/fixes
2021-04-03 18:09:20 +01:00
Dimitrie Stefanescu 8dc8c9a0fb Merge branch 'main' into dim/fixes 2021-04-03 18:07:21 +01:00
Dimitrie Stefanescu 65fab58006 feat: thread safety 2021-04-03 18:04:53 +01:00
Dimitrie Stefanescu b93aa1a76a feat(cancellation): adds readme example 2021-04-03 18:03:30 +01:00
Dimitrie Stefanescu bdc105fa95 feat(cancellation): implements it cyclotron component 2021-04-03 17:59:58 +01:00
Dimitrie Stefanescu 59f87b2a7d feat(cancellation): exposes cancellation request (example in prime calculator) 2021-04-03 17:56:39 +01:00
Dimitrie Stefanescu c314fc5834 feat(cancellation): exposes task cancellation method 2021-04-03 17:10:36 +01:00
Alan Rynne b7e57b1ec8 fix(0.2.1): Missmatched GH versions 2021-02-03 22:53:09 +01:00
Alan Rynne 0f649d6d2d release(0.2.1): Bumped version
Should also publish nuget symbols package for easier debugging!
2021-02-03 22:38:15 +01:00
Dimitrie Stefanescu 92f814dab4 fix(threading): fixes race condition in settings vars on worker done() calls
changes SetData to int value (1=setting, 0=not setting), and moves all ops to interlocked calls on
ints - fingers crossed
2021-02-03 19:49:21 +00:00
9 changed files with 143 additions and 50 deletions
+1 -1
View File
@@ -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
+79 -30
View File
@@ -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();
@@ -84,7 +88,11 @@ namespace GrasshopperAsyncComponent
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 +116,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 +185,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 +200,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.28.20199.17141</Version>
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="RhinoCommon">
<Version>6.29.20238.11501</Version>
<Version>6.28.20199.17141</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>1.2.3.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>Adds cancellation methods.</releaseNotes>
<copyright>Speckle Systems</copyright>
<tags>grasshopper rhino mcneel gh_component</tags>
</metadata>
@@ -9,7 +9,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Speckle Systems")]
[assembly: AssemblyProduct("GrasshopperAsyncComponent")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyCopyright("Copyright AEC Systems © 2020, 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyVersion("1.2.3.0")]
[assembly: AssemblyFileVersion("1.2.3.0")]
@@ -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.28.20199.17141" />
<PackageReference Include="RhinoCommon" Version="6.28.20199.17141" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GrasshopperAsyncComponent\GrasshopperAsyncComponent.csproj">
@@ -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,15 @@ namespace GrasshopperAsyncComponentDemo.SampleImplementations
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddNumberParameter("Output", "O", "The n-th prime number.", GH_ParamAccess.item);
}
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
{
base.AppendAdditionalMenuItems(menu);
Menu_AppendItem(menu, "Cancel", (s, e) =>
{
RequestCancellation();
});
}
}
@@ -44,7 +53,7 @@ namespace GrasshopperAsyncComponentDemo.SampleImplementations
public override void DoWork(Action<string, double> ReportProgress, Action Done)
{
// 👉 Checking for cancellation!
if (CancellationToken.IsCancellationRequested) return;
if (CancellationToken.IsCancellationRequested) { return; }
int count = 0;
long a = 2;
@@ -53,14 +62,14 @@ namespace GrasshopperAsyncComponentDemo.SampleImplementations
while (count < TheNthPrime)
{
// 👉 Checking for cancellation!
if (CancellationToken.IsCancellationRequested) return;
if (CancellationToken.IsCancellationRequested) { 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) {return; }
if (a % b == 0)
{
@@ -98,7 +107,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);
}
@@ -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) { 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) { return; }
}
Done();
+23 -3
View File
@@ -15,14 +15,15 @@ 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
Provides an abstract `GH_AsyncComponent` which you can inherit from to scaffold your own async component. There's more info in the [blogpost](https://speckle.systems/blog/async-gh/) on how to go about it.
Even better, there's a [sample component that shows how an implementation could look like](https://github.com/specklesystems/GrasshopperAsyncComponent/tree/main/GrasshopperAsyncComponent/SampleImplementations)! There's two components in that folder:
- The Useless Spinner: does no meaningfull CPU work, just keeps a thread busy with SpinWait()
- The N-th Prime Calculator: can actually spin your computer's fans quite a bit (for numbers > 100.000)
> #### Checkout the sample implementation!
> - [Prime number calculator](https://github.com/specklesystems/GrasshopperAsyncComponent/blob/a53cef31a8750a18d06fad0f41b2dc452fdc253b/GrasshopperAsyncComponentDemo/SampleImplementations/Sample_PrimeCalculatorAsyncComponent.cs#L11-L53) Calculates the n'th prime. Can actually spin your computer's fans quite a bit for numbers > 100.000!
> - [Usless spinner](https://github.com/specklesystems/GrasshopperAsyncComponent/blob/2f2be53bffd2402337ba40d65bb5b619d1161b3e/GrasshopperAsyncComponentDemo/SampleImplementations/Sample_UslessCyclesComponent.cs#L13-L91) does no meaningfull CPU work, just keeps a thread busy with SpinWait().
### Current limitations
@@ -46,6 +47,25 @@ Q: Does this component use all my cores? A: OH YES. It goes WROOOM.
![image](https://user-images.githubusercontent.com/7696515/95597125-29310900-0a46-11eb-99ce-663b34506a7a.png)
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: