Hi @dale
Before I burn my fingers, I wanted to ask if anyone has had any experience of what works and what doesn’t.
Is it possible to pass in-memory BREP’s over a thread boundary into a new thread and return the result BREP’s?
I was not sure because I read this post:
I just gave it a shot. And it seems to work!
TaskFactory.StartNew() offers the possibility to pass a CancellationToken
Here is the example code:
protected override Result RunCommand(RhinoDoc doc, RunMode mode)
{
try
{
RhinoApp.WriteLine(
$"Executing the command {EnglishName} on ManagedThreadId={Thread.CurrentThread.ManagedThreadId}. Should be 1 for the UI tread");
BoundingBox boundingBox1 = new BoundingBox(0, 0, 0, 10, 10, 10);
BoundingBox boundingBox2 = new BoundingBox(5, 5, 5, 15, 15, 15);
Brep brep1 = Brep.CreateFromBox(boundingBox1);
Brep brep2 = Brep.CreateFromBox(boundingBox2);
List<Brep> breps = new List<Brep>();
breps.Add(brep1);
breps.Add(brep2);
var result = Task<List<Brep>>.Factory.StartNew(() => CalculateBooleanUnionOnDifferentThread(breps), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
result.Wait();
foreach (var brep in result.Result)
{
doc.Objects.AddBrep(brep);
}
doc.Views.Redraw();
RhinoApp.WriteLine($"The {EnglishName} added {result.Result.Count} brep(s) to the document");
// ---
return Result.Success;
}
catch (Exception e)
{
RhinoApp.WriteLine(e.ToString());
throw;
}
}
private static List<Brep> CalculateBooleanUnionOnDifferentThread(List<Brep> breps)
{
RhinoApp.WriteLine($"Create boolean union for {breps.Count} breps on ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
return Brep.CreateBooleanUnion(breps, 0.001).ToList();
}
The output looks like this
Executing the command RhinoThreadingSamplesCommand on ManagedThreadId=1. Should be 1 for the UI tread
Create boolean union for 2 breps on ManagedThreadId=18
The RhinoThreadingSamplesCommand added 1 brep(s) to the document
What I don’t understand: How is it possible that a BREP created via P/Invoke in an umnanaged library can be moved across the thread boundary.
I was of the opinion that only plain old CLR objects (POCO’s) can be passed.
Here is the example where the task is aborted by the cancellation token. If someone has a better idea than Thread.Abort() input would be welcome.
protected override Result RunCommand(RhinoDoc doc, RunMode mode)
{
try
{
// TODO: start here modifying the behaviour of your command.
// ---
RhinoApp.WriteLine(
$"Executing the command {EnglishName} on ManagedThreadId={Thread.CurrentThread.ManagedThreadId}. Should be 1 for the UI tread");
BoundingBox boundingBox1 = new BoundingBox(0, 0, 0, 10, 10, 10);
BoundingBox boundingBox2 = new BoundingBox(5, 5, 5, 15, 15, 15);
Brep brep1 = Brep.CreateFromBox(boundingBox1);
Brep brep2 = Brep.CreateFromBox(boundingBox2);
List<Brep> breps = new List<Brep>();
breps.Add(brep1);
breps.Add(brep2);
var cancellationSource = new CancellationTokenSource();
var cancellationToken = cancellationSource.Token;
var task = Task<List<Brep>>.Factory.StartNew(() => CalculateBooleanUnionOnDifferentThread(breps, cancellationToken), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
// wait for a certain time until the task is finished
if (task.Wait(1000))
{
foreach (var brep in task.Result)
{
doc.Objects.AddBrep(brep);
}
doc.Views.Redraw();
RhinoApp.WriteLine(
$"The {EnglishName} added {task.Result.Count} brep(s) to the document. ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
return Result.Success;
}
else
{
// task did not complete execution within the allotted time
// cancel the thread
cancellationSource.Cancel();
//todo: catch the ThreadAbortException
}
return Result.Failure;
}
catch (Exception e)
{
RhinoApp.WriteLine(e.ToString());
return Result.Failure;
}
}
private static List<Brep> CalculateBooleanUnionOnDifferentThread(List<Brep> breps,
CancellationToken cancellationToken)
{
var thread = Thread.CurrentThread;
RhinoApp.InvokeOnUiThread(new Action(() => RhinoApp.WriteLine($"Create boolean union for {breps.Count} breps on ManagedThreadId={thread.ManagedThreadId}")));
//Register the cancel action
cancellationToken.Register(() =>
{
RhinoApp.InvokeOnUiThread(new Action(() => RhinoApp.WriteLine($"Aborting the thread. ManagedThreadId={thread.ManagedThreadId}")));
// It is not recommended to use Thread.Abort()... but could not find a better solution
thread.Abort();
});
while (true)
{
// very long running operation
// Brep.CreateBooleanUnion(breps, 0.001).ToList();
}
return null;
}
It would be nice if RhinoCommon would support natively the cancellation of operations.
At least for some of the potentially long running operations.