首页 > 代码库 > 通俗理解 grasshopper 触发更新 / 多线程处理
通俗理解 grasshopper 触发更新 / 多线程处理
掉进了 grasshopper 的坑真是一把辛酸泪。
下面是 grasshopper 平台上讨论的一个问题:
http://www.grasshopper3d.com/forum/topics/triggering-solution-refresh
大牛 David Rutten 的一段评论,我翻译如下,仅供交流:
当然这是2011年的事情了,后续更新我会补上!!
1. ExpireSolution(True) vs. ExpireSolution(False) vs. NewSolution(True) vs. NewSolution(False)
当你结束调用的时候,该调用哪一个或哪一些方法?这基本上取决于你希望这些事件发生的频率有多高,以及你是想更新单个组件还是一次性更新多个组件。这几个方法分别是这样做的:
ExpireSolution(True):你调用这个方法时所在的组件以及依赖于它的所有组件,会被设置“终止”标志,基本上等于它的下游。当这些标志都设置完成后,它会调用 GH_Document.NewSolution(False)。
ExpireSolution(False):几乎同上,除了它不会通知文件去重算自身。如果你想在启动一次新的计算过程之前终止掉所有组件,那么就用这个方法。
NewSolution(True):这会设置文件中所有组件的“终止”标志,并且重算一切,这是地毯式轰炸,你最好别用。
NewSolution(False):这会重算那些被设置了“终止”标志的对象。
总结一下,在以下这些场景中你该这样去用:
- 一个对象有改变,我们想立即重算它(比如用户拖动一个滑块,或者连接了一条线)。调用 ExpireSolution(True)
- 由于某些场外因素,一票对象同时(或几乎同时)终止了。例如,你想挂载多个硬件控制器或传感器并且他们一直在触发更新。在所有受影响的组件上调用 ExpireSolution(False),然后调用一次 NewSolution(False)
- 一个大事件发生了但是你并不知道发生了什么。调用 NewSolution(True),因为你并没有足够的信息去阻止缓存数据被破坏。
grasshopper 没有足够智能去推迟计算过程,直到它确定没有 ExpireObject() 的调用进来了。如果你让他去重算,它就会立即重算。如果你需要智能地延迟,你需要自行编写代码。
2. 基于线程的应用 vs. 不基于线程的应用
grasshopper 是一个不基于线程的应用,顾名思义,任何框架应用的接口部分都是单线程的。这在当你接收到另外一个线程上的外部事件时就会出现问题,有可能是硬件控制信号,有可能是传感器数据。
实际情况是这样的:
由于某些原因,grasshopper 文件被终止了,紧接着 NewSolution() 方法会被调用以期修正这个未知的错误。首先它会依次查找文件中的组件,每当发现一个组件的“终止”标志为 true,就会告诉它重新计算。如果一个组件所依赖的组件没有计算完成(‘solved’),它是没办法重新计算的。因此可能会导致整个文件的重算冲击波。最后,当所有输入可用,这个组件终于重算了之后,将把控制权交还给文件求解器。
文件求解器现在继续寻找它的“已终止”的组件。它可能遇到的是上述这种情况,也可能本来只有一个终止的组件,也可能找到的那个组件已经让其他终止组件重算了,如此一来,文件求解器就没什么事干了。
当文件求解器对所有对象都检查过了,没有“终止”的组件的时候,它会调用 grasshopper canvas 和 rhino viewports 进行重绘。
这就是一个正确的、单线程的求解过程。如果有两个或以上的线程在同一个文件中执行,简直打开了地狱大门。
让我们来设想一下,文件求解器正在解决已终止对象的问题,这时候另一个线程的外部事件发生了,这个事件马上就被相应的组件响应处理了。这个组件此时调用了 ExpireSolution(True),导致了一票组件的“终止”标志被设置。其中一些组件可能早被文件处理器在第一个线程中处理过了,即使文件处理器认为自己把他们完美处理了,然而他们还是又一次终止了。其他的组件可能由于文件处理器还没有到达他们那里,所以还是在“终止”状态。最坏的情况是,某个组件在第一个线程中已经计算出结果并成功被后续的数据流接收,在接收的过程中第二个线程把数据擦除了,这会导致一次不匹配,很有可能会崩溃。
grasshopper 核心算法并不是线程安全的,如果你调用了不同线程的方法,你会陷入崩溃。
你需要做的是从你的第二个线程里面“调用”你的第一个线程,这样第一个线程就可以在响应你的请求之前把它正在做的事做完。在你用 GUI 写一个多线程应用的时候,调用是一个非常重要的概念,网上有很多资源解释它是如何工作的,下面是其中的2篇:
http://weblogs.asp.net/justin_rogers/pages/126345.aspx
http://www.yoda.arachsys.com/csharp/threads/winforms.shtml
原文:
Hi Eric,
there‘s multiple issues here, I‘ll address them in separate posts.
1) ExpireSolution(True) vs. ExpireSolution(False) vs. NewSolution(True) vs. NewSolution(False)
Which method(s) you end up calling depends a lot on how often you expect to get these events and whether or not you want to update a single or multiple components at the same time. This is what the methods do respectively:
ExpireSolution(True): It sets the ‘expired‘ flag on the component you call this method on and all objects that depend on that component. Basically everything downstream of it. When it‘s done setting all these ‘expired‘ flags, it will place a call to GH_Document.NewSolution(False).
ExpireSolution(False): Does the same as the previous one, except it doesn‘t tell the document to re-solve itself. Use this method if you want to expire a bunch of different components before attempting a new solution.
NewSolution(True): This will set the ‘expired‘ flags of all components inside the document and resolves everything. This is carpet bombing, rarely should you use this method.
NewSolution(False): This will only resolve those objects that have their ‘expired‘ flags set.
So, to recap, given the following cases you should be using the associated approach:
- A single object was changed and we want to immediately resolve (for example when a user drags a number slider, or connects a wire). Use ExpireSolution(True).
- A bunch of objects are expired simultaneously (or nearly simultaneously) by some sort of massive off-site event. For example if you hook up multiple hardware controls or sensors and they‘re all firing updates all the time. Use ExpireSolution(False) on all the affected objects, then place a single call to NewSolution(False).
- Something massive has happened and you have no idea of what exactly it was. Use NewSolution(True), as you don‘t have enough information to limit the devastation of cached data.
Grasshopper is not smart enough to post-pone solutions briefly until it decided that no more calls to ExpireObject() are coming in. If you tell it to recompute, it will immediately recompute. If you need delayed smarts, you‘ll need to write those yourself.
2) Threaded application vs. Non-Threaded applications.
Grasshopper is a non-threaded application and by definition, the interface portion of any winforms app is single-threaded. This poses problems when you get external events that occur on a different thread, as might well be the case for hardware controls or sensor data.
Here‘s essentially what happens:
Something expires (part of) a Grasshopper document, and the NewSolution() method is called upon to rectify this. It starts by iterating over all the objects inside the document and whenever it encounters an object which has the ‘expired‘ flag set to true, it will tell it to recompute itself. This object cannot recompute itself unless all the objects it depends on are also ‘solved‘, so it might cause a shock-wave of recomputes across the entire document. Eventually though all the inputs are available and it recomputes itself and then hands control back to the document solver.
The document solver now continues on its quest to find more ‘expired‘ objects. It might find some in which case the above story is repeated, or maybe there was only ever one expired object, or maybe the object we just found caused all other expires objects to solve themselves, leaving nothing to do for the document solver.
When the document solver has iterated over all the objects and determined that none of them are still ‘expired‘, it will place a call to redraw the Grasshopper canvas and the Rhino viewports.
That is what happens during a proper, single-threaded solution. When there‘s two (or more) threads operating on the same document, all hell breaks loose:
Let‘s assume the document solver is halfway done with its job of resolving all expired objects. At this point an external event comes in on a different thread, which is handled immediately by some component. This component places a call to ExpireSolution(True), which sets the ‘expired‘ flags of a bunch of components. Some of these components will already have been handled by the solver in the first thread, so even though the solver thinks all is fine and dandy, these components have in fact expired again. Other components might still be expired because the solver hasn‘t gotten to them yet. Worst case scenario, a component just finished solving itself on the first thread and the data inside of it is being harvested by a downstream component. While it‘s collecting this data the second thread wipes the data, causing a mismatch and probably a crash.
The Grasshopper core algorithms are not thread-safe and if you start calling methods from different threads, you will run into clashes.
What you need to do is ‘invoke‘ the first thread from your second thread. That way the first thread is allowed to finish what it‘s doing before getting around to your request. Invoking is a very important concept when you‘re writing a multi-threaded app with a GUI and there‘s plenty of online resources explaining how it works. Here‘s two:
http://weblogs.asp.net/justin_rogers/pages/126345.aspx
http://www.yoda.arachsys.com/csharp/threads/winforms.shtml
--
David Rutten
david@mcneel.com
Poprad, Slovakia
通俗理解 grasshopper 触发更新 / 多线程处理