如何让Grasshopper Component创建后台进程,并对结果进行更新
David Rutten的原文解答
If you really must use a thread to process data while you keep the Grasshopper GUI alive, then the approach would be something like the following:
- Your component is called upon to Solve itself for a Grasshopper Solution.
- It determines that a lot of data needs to be handled, so it starts a thread to do the heavy lifting and yields execution right back at Grasshopper, which then continues with null data (you’ll get a lot of orange components).
- Your thread is done processing data, it now wants to insert this data into the Grasshopper network, so it must Expire the original component, but it can only do this if no solution is running. So, it has to loop indefinitely until it finds a moment when the GH_Document.SolutionState is not GH_ProcessStep.Process. It should also check to see whether the GH_Document that contains the Component is in fact still in existence and active and whether or not the original component is still available or if it’s been deleted in the mean time.
- The thread must now Invoke a method on the Grasshopper GUI thread that will expire your component and initiate a new solution. If you call ExpireSolution(True) from a background thread, you will crash. Not just you, you will also crash me and probably Rhino. You can never, never ever call code that results in UI changes from a thread other than the main UI thread.
- Your component has now been expired and a new solution is running. It must have some mechanism in place which tells it whether it should start a new Background thread (a la point #2) or whether the thread it started last time is now ready for harvest. If the latter, extract the data from the thread and fill out the output parameters.
——David Rutton @ https://www.grasshopper3d.com/forum/topics/possible-to-add-background?xg_raw_resources=1&id=2985220%3ATopic%3A63596&page=1
关键点有3个:
- 判断当前画布上没有任何一个component正在running
- 利用GUI线程去调用ExpireSolution方法(如果有别的component正在running,调用ExpireSolution就会导致程序崩溃,这也是为什么要在第1点中判断)
- 这个component本身要能够判断后台线程是否计算完成
下面的示例代码中没有做第1步,只说明了2、3是如何做到的
示例代码
bool run = false;
bool havested = true;
int result = 0;
protected override void SolveInstance(IGH_DataAccess DA)
{
if (run)
{
// Here to determine if a new solution had been triggered while
// last solution is still running.
// either do something to cancel current running thread and start a new one
// <!! do not forget to set up flags !!>
// run = false;
// havested = true;
// or ignore the new solution request
// AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Running!");
// return;
}
if (havested)
{
int steps = 0;
if (!DA.GetData(0, ref steps)) return;
Message = "In progress";
Action exp = () =>
{
OnPingDocument().FindComponent(this.InstanceGuid).ExpireSolution(true);
};
Action<ThisComponentClass, IGH_DataAccess, int> action = (comp, da, st) =>
{
try
{
int res = GoSolve(st);
comp.Message = "Calc Completed";
this.result = res;
Grasshopper.Instances.DocumentEditor.Invoke(exp);
}
catch (OperationCanceledException)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Canceled.");
comp.havested = true;
comp.run = false;
return;
}
};
Task t = Task.Run(() => action.Invoke(this, DA, steps), cToken);
run = true;
havested = false;
return;
}
DA.SetData(0, result);
run = false;
havested = true;
}
private int GoSolve(int steps)
{
int res = 0;
for (int i = 0; i < steps; i++)
{
res += i;
if (GH_Document.IsEscapeKeyDown())
{
throw new OperationCanceledException();
}
Thread.Sleep(100);
}
return res;
}
其中第2点如何利用GUI线程去调用ExpireSolution是使用Grasshopper.Instances.DocumentEditor.Invoke方法,可以写成
Grasshopper.Instances.DocumentEditor.Invoke(
new Action( () =>
{
OnPingDocument().FindComponent(this.InstanceGuid).ExpireSolution(true);
}
));