Pausing and Resuming Threads
Having one thread (e.g. your main thread) telling another thread to pause and resume is actually quite a challenge. Threads can put themselves to sleep, but can’t send each other to sleep. My usage case is to pause the generation threads when players enter a level, to free up CPU time for gameplay systems that are pushed to other threads. In general I expect that I’d almost always want to pause and resume worker threads from the main Unity thread.
In this case I can’t simply wait for the threads to end on their own (as we did last time), because that could block for seconds. Instead, I’m using WaitHandles to signal a block, and implement the thread as an enumerator, so we can define regular sync points easily.
Implementing Threads as Enumerators
If you’ve ever worked with coroutines in Unity, you’ll be familiar with functions that return IEnumerator
. If you’re not familiar, or need a refresher, Unity’s official Coroutines tutorial gives a good overview:
If we implement our threads as enumerators, then we can implement a policy where the thread can be paused at any yield. This allows the thread algorithm to be written in a very neat way (with no knowledge of it’s thread context), yet still define clean points to pause.
Implementing ThreadWorker with Enumerators
We’ll change the Action
to be an IEnumerator
and add a WaitHandle
, for blocking the thread.
using System.Collections; using System.Threading; public class ThreadWorker { private Thread ChildThread = null; private EventWaitHandle SuspendHandle = new EventWaitHandle(true, EventResetMode.ManualReset); public void Start(IEnumerator threadImplementation) { ChildThread = new Thread(ThreadLoop); ChildThread.Start(threadImplementation); } public void Resume() { SuspendHandle.Set(); } public void Suspend() { SuspendHandle.Reset(); } private void ThreadLoop(object threadImplementation) { var impl = threadImplementation as IEnumerator; while(impl.MoveNext()) SuspendHandle.WaitOne(); ChildThread = null; } // etc.
Calling MoveNext()
on the Enumerator runs it until the next yield. Unity coroutines do this once per frame, but here I’m stepping it continuously in a while loop, checking SuspendHandle
on each yield.
When Suspend()
is called, the WaitHandle gets reset, and will cause a block on the next yield. This block does not happen immediately and the thread calling Suspend() does not wait for that block to happen. Think of a call to Suspend() as telling the ChildThread to “suspend when you’re ready”, or “suspend on next yield”. That ChildThread will block until Resume() is called, resetting the handle.