Creating The Thread
I went through a few iterations of implementing the threaded system, and discarded some apparently obvious solutions as unworkable. My first attempt was to create a new thread each frame, let it do its work and then die. However, creating a thread is very slow (almost exactly the same time as the flow field update) and generates around 500B of garbage.
My second attempt was to use the BackgroundWorker system, which pools threads to avoid the setup time. However, while fast, using a BackgroundWorker still produced around 500B of garbage each frame.
Chosen Solution
My solution was to go a little bit lower level. I create a thread, that runs a simple, infinite loop, then synchronise that loop with the main Update() loop in Unity. Which gives me something looking a bit like this:
using System.Threading; using UnityEngine; public class ThreadedBehaviour : MonoBehaviour { Thread ChildThread = null; EventWaitHandle ChildThreadWait = new EventWaitHandle(true, EventResetMode.ManualReset); EventWaitHandle MainThreadWait = new EventWaitHandle(true, EventResetMode.ManualReset); void ChildThreadLoop() { ChildThreadWait.Reset(); ChildThreadWait.WaitOne(); while(true) { ChildThreadWait.Reset(); // Do Update WaitHandle.SignalAndWait(MainThreadWait, ChildThreadWait); } } void Awake() { ChildThread = new Thread(ChildThreadLoop); ChildThread.Start(); } void Update() { MainThreadWait.Reset(); WaitHandle.SignalAndWait(ChildThreadWait, MainThreadWait); } }
Script Overview
The important thing here are the two EventWaitHandle variables, which are used to sync the threads. If a thread requests to wait on a wait handle that is Reset (e.g. line 13), it will block until Set() is called on that EventWaitHandle by another thread. The SignalAndWait() function is the same as calling Set() and WaitOne() on the two parameters (effectively releasing the other thread, and blocking the current one).
On Awake() our ThreadedBehaviour class will create the child thread and start it, which will begin running the code in ChildThreadLoop() and immediately wait on the ChildThreadWait. It will remain in that blocked state until the Update() function is called.
Script Behaviour
In Update(), we unblock the child thread and block ourselves until the child has completed (line 34). Once the thread completes its work, it will unblock the main thread and block itself again (line 21). Which looks like:
So right now, we are doing the flow update on another thread, but we still wait for the work to be done. We haven’t saved any time on the main thread; we’re actually a little slower because there is a small overhead in the synchronisation actions. But we have pushed some work to another thread, and we know how to synchronise threads. So this is a couple of big steps on the road to getting them running in parallel.