Mastering C# and Unity3D

Using the Unity API from Other Threads

The Unity API can mostly only be used from the main thread. This is used as an excuse by Unity developers to write all their code on the main thread. This makes their code run 2-6x slower. So what can we do about it? Today’s article presents a simple way to use the Unity API from other threads. Read on to learn how to unlock all that extra CPU power!

There’s nothing we can do about Unity requiring us to access its API from only the main thread. Sure, there are exceptions to this rule such as using Vector3 from any thread, but generally we can’t do anything useful like access the hierarchy outside the main thread.

So what we need is a way for a non-main (“worker”) thread to tell the main thread to access the Unity API. Then we need the main thread to communicate its results back to the worker thread.

Doing this is actually pretty simple. It allows us to set up an easily-extensible pattern that we can use to support any Unity API functionality we want to use from other threads. It’s type-safe, thread-safe, and easy-to-use. Let’s break it down into parts.

First, the commands. These are messages from worker threads to the main thread telling the main thread what the worker thread wants to do. A command is simply a function call that puts an instance of a BaseCommand class into a Queue<BaseCommand>. Later on the main thread will go through this queue of commands and execute them.

Now for the other side: results. This is how the main thread tells a worker thread that it’s done processing one of its commands. A Result<T> simply wraps up the result value of type T and uses an AutoResetEvent to make the worker thread sleep until the result value is ready.

Of course we don’t want this system to raise the ire of the garbage collector, or we’ll be giving back some of the performance benefits of using threads in the first place. So it’s important to talk about how we’re not going to need to create and release these BaseCommand and Result objects.

BaseCommand objects are internally pooled by the MainThreadQueue class that’s at the heart of this system. Worker threads don’t need to worry about it at all. In fact, the command class types are private so they’re not even exposed outside the MainThreadQueue class.

Result objects are passed in by the worker thread. They can be reused after the command finishes. Exactly how to reuse them is up to the worker thread because only it knows when its done using the result.

With this in mind, let’s look at a little script that uses this system. The script’s job is to create a bunch of game objects and set their positions. It does this with four threads that all share a MainThreadQueue object. As you read, notice how there’s no manual thread synchronization with locks or mutexes. It’s not quite as direct as using the Unity API normally, but it’s pretty straightforward:

There are a couple more aspects of this to notice. First, the Result objects are reused by the threads. There’s no need to pool them in this case and the worker thread didn’t even need to call Reset because MainThreadQueue takes care of that when you queue a command.

Also, when the script’s Update runs MainThreadQueue.Execute it can pass in a time limit. This allows us to put a cap on how much time we want to spend executing commands per frame. We can use this to easily spread out work across multiple frames because the remaining work simply sits in the queue to be executed on the next Update of the script.

Now let’s look at the full source code for this system. It’s heavily-commented, but still under 500 lines in just one file. Feel free to drop it in your projects as it’s MIT-licensed.

usingSystem.Collections.Generic;usingSystem.Diagnostics;usingSystem.Threading;usingUnityEngine;usingUnityEngine.Assertions;/// <summary>/// A queue of commands to execute on the main thread. Each command function (e.g. NewGameObject)/// takes a Result parameter that initially has no value but gets a value after the command/// executes./// </summary>/// <author>http://JacksonDunstan.com/articles/3930</author>/// <license>MIT</license>publicclass MainThreadQueue
{/// <summary>/// Result of a queued command. Will have a Value when it IsReady./// </summary>publicclass Result<T>{private T value;privatebool hasValue;private AutoResetEvent readyEvent;public Result(){
readyEvent =new AutoResetEvent(false);}/// <summary>/// Result value. Blocks until IsReady is true./// </summary>public T Value{get{
readyEvent.WaitOne();returnvalue;}}/// <summary>/// Check if the result value is ready./// </summary>publicbool IsReady
{get{return hasValue;}}/// <summary>/// Set the result value and flag it as ready./// This is meant to be called by MainThreadQueue only./// </summary>/// <param name="value">/// The result value/// </param>publicvoid Ready(T value){this.value=value;
hasValue =true;
readyEvent.Set();}/// <summary>/// Reset the result so it can be used again./// </summary>publicvoid Reset(){value=default(T);
hasValue =false;}}/// <summary>/// A result with no value (i.e. for a function returning "void")/// </summary>publicclass Result
{privatebool hasValue;private AutoResetEvent readyEvent;public Result(){
readyEvent =new AutoResetEvent(false);}/// <summary>/// If the command has been executed/// </summary>publicbool IsReady
{get{return hasValue;}}/// <summary>/// Mark the result as ready to indicate that the command has been executed./// </summary>publicvoid Ready(){
hasValue =true;
readyEvent.Set();}/// <summary>/// Blocks until IsReady is true/// </summary>publicvoid Wait(){
readyEvent.WaitOne();}/// <summary>/// Reset the result so it can be used again./// </summary>publicvoid Reset(){
hasValue =false;}}/// <summary>/// Types of commands/// </summary>privateenum CommandType
{/// <summary>/// Instantiate a new GameObject/// </summary>
NewGameObject,
/// <summary>/// Get a GameObject's transform/// </summary>
GetTransform,
/// <summary>/// Set a Transform's position/// </summary>
SetPosition
}/// <summary>/// Base class of all command types/// </summary>privateabstractclass BaseCommand
{/// <summary>/// Type of the command/// </summary>public CommandType Type;}/// <summary>/// Command object for instantiating a GameObject/// </summary>privateclass NewGameObjectCommand : BaseCommand
{/// <summary>/// Name of the GameObject/// </summary>publicstring Name;/// <summary>/// Result of the command: the newly-instantiated GameObject/// </summary>public Result<GameObject> Result;public NewGameObjectCommand(){
Type = CommandType.NewGameObject;}}/// <summary>/// Command object for getting a GameObject's transform/// </summary>privateclass GetTransformCommand : BaseCommand
{/// <summary>/// GameObject to get the Transform for/// </summary>public GameObject GameObject;/// <summary>/// Result of the command: the GameObject's transform./// </summary>public Result<Transform> Result;public GetTransformCommand(){
Type = CommandType.GetTransform;}}/// <summary>/// Set a Transform's position/// </summary>privateclass SetPositionCommand : BaseCommand
{/// <summary>/// Transform to set the position of/// </summary>public Transform Transform;/// <summary>/// Position to set to the Transform/// </summary>public Vector3 Position;/// <summary>/// Result of the command: no value/// </summary>public Result Result;public SetPositionCommand(){
Type = CommandType.SetPosition;}}// Pools of command objects used to avoid creating more than we needprivate Stack<NewGameObjectCommand> newGameObjectPool;private Stack<GetTransformCommand> getTransformPool;private Stack<SetPositionCommand> setPositionPool;// Queue of commands to executeprivate Queue<BaseCommand> commandQueue;// Stopwatch for limiting the time spent by Executeprivate Stopwatch executeLimitStopwatch;/// <summary>/// Create the queue. It initially has no commands./// </summary>public MainThreadQueue(){
newGameObjectPool =new Stack<NewGameObjectCommand>();
getTransformPool =new Stack<GetTransformCommand>();
setPositionPool =new Stack<SetPositionCommand>();
commandQueue =new Queue<BaseCommand>();
executeLimitStopwatch =new Stopwatch();}/// <summary>/// Get an object from a pool or create a new one if none are available./// This function is thread-safe./// </summary>/// <returns>/// An object from the pool or a new instance/// </returns>/// <param name="pool">/// Pool to get from/// </param>/// <typeparam name="T">/// Type of pooled object/// </typeparam>privatestatic T GetFromPool<T>(Stack<T> pool)where T :new(){lock(pool){if(pool.Count>0){return pool.Pop();}}returnnew T();}/// <summary>/// Return an object to a pool./// This function is thread-safe./// </summary>/// <param name="pool">/// Pool to return to/// </param>/// <param name="obj">/// Object to return/// </param>/// <typeparam name="T">/// Type of pooled object/// </typeparam>privatestaticvoid ReturnToPool<T>(Stack<T> pool, T obj){lock(pool){
pool.Push(obj);}}/// <summary>/// Queue a command. This function is thread-safe./// </summary>/// <param name="cmd">/// Command to queue/// </param>privatevoid QueueCommand(BaseCommand cmd){lock(commandQueue){
commandQueue.Enqueue(cmd);}}/// <summary>/// Queue a command to instantiate a GameObject/// </summary>/// <param name="name">/// Name of the GameObject. Must not be null./// </param>/// <param name="result">/// Result to be filled in when the command executes. Must not be null./// </param>publicvoid NewGameObject(string name,
Result<GameObject> result){
Assert.IsTrue(name !=null);
Assert.IsTrue(result !=null);
result.Reset();
NewGameObjectCommand cmd = GetFromPool(newGameObjectPool);
cmd.Name= name;
cmd.Result= result;
QueueCommand(cmd);}/// <summary>/// Queue a command to get a GameObject's transform/// </summary>/// <param name="go">/// GameObject to get the transform from. Must not be null./// </param>/// <param name="result">/// Result to be filled in when the command executes. Must not be null./// </param>publicvoid GetTransform(
GameObject go,
Result<Transform> result){
Assert.IsTrue(go !=null);
Assert.IsTrue(result !=null);
result.Reset();
GetTransformCommand cmd = GetFromPool(getTransformPool);
cmd.GameObject= go;
cmd.Result= result;
QueueCommand(cmd);}/// <summary>/// Queue a command to set a Transform's position/// </summary>/// <param name="transform">/// Transform to set the position of/// </param>/// <param name="position">/// Position to set to the transform/// </param>/// <param name="result">/// Result to be filled in when the command executes. Must not be null./// </param>/// <param name="result">/// Result to be filled in when the command executes. Must not be null./// </param>publicvoid SetPosition(
Transform transform,
Vector3 position,
Result result){
Assert.IsTrue(transform !=null);
Assert.IsTrue(result !=null);
result.Reset();
SetPositionCommand cmd = GetFromPool(setPositionPool);
cmd.Transform= transform;
cmd.Position= position;
cmd.Result= result;
QueueCommand(cmd);}/// <summary>/// Execute commands until there are none left or a maximum time is used/// </summary>/// <param name="maxMilliseconds">/// Maximum number of milliseconds to execute for. Must be positive./// </param>publicvoid Execute(int maxMilliseconds =int.MaxValue){
Assert.IsTrue(maxMilliseconds >0);// Process commands until we run out of time
executeLimitStopwatch.Reset();
executeLimitStopwatch.Start();while(executeLimitStopwatch.ElapsedMilliseconds< maxMilliseconds){// Get the next queued command, but stop if the queue is empty
BaseCommand baseCmd;lock(commandQueue){if(commandQueue.Count==0){break;}
baseCmd = commandQueue.Dequeue();}// Process the command. These steps are followed for each command:// 1. Extract the command's fields// 2. Reset the command's fields// 3. Do the work// 4. Return the command to its pool// 5. Make the result readyswitch(baseCmd.Type){case CommandType.NewGameObject:{// Extract the command's fields
NewGameObjectCommand cmd =(NewGameObjectCommand)baseCmd;string name = cmd.Name;
Result<GameObject> result = cmd.Result;// Reset the command's fields
cmd.Name=null;
cmd.Result=null;// Return the command to its pool
ReturnToPool(newGameObjectPool, cmd);// Do the work
GameObject go =new GameObject(name);// Make the result ready
result.Ready(go);break;}case CommandType.GetTransform:{// Extract the command's fields
GetTransformCommand cmd =(GetTransformCommand)baseCmd;
GameObject go = cmd.GameObject;
Result<Transform> result = cmd.Result;// Reset the command's fields
cmd.GameObject=null;
cmd.Result=null;// Return the command to its pool
ReturnToPool(getTransformPool, cmd);// Do the work
Transform transform = go.transform;// Make the result ready
result.Ready(transform);break;}case CommandType.SetPosition:{// Extract the command's fields
SetPositionCommand cmd =(SetPositionCommand)baseCmd;
Transform transform = cmd.Transform;
Vector3 position = cmd.Position;
Result result = cmd.Result;// Reset the command's fields
cmd.Transform=null;
cmd.Position= Vector3.zero;
cmd.Result=null;// Return the command to its pool
ReturnToPool(setPositionPool, cmd);// Do the work
transform.position= position;// Make the result ready
result.Ready();break;}}}}}

The above code is just a starting place for exposing the Unity API to worker threads. It only supports the three commands used in the test script: “new game object”, “get game object’s transform”, “set transform’s position”. To be useful in a real game, you’ll need to add many more commands. Thankfully, that’s easy!

First, add a new entry to the CommandType enum for your new command. Then add a new class extending BaseCommand. Use the other command classes as a pattern. Remember to make it private and set Type = CommandType.MyNewCommand in the constructor.

Next, add a new pool field and initialize it in the constructor. Just copy/paste from the others and change the command type.

Now add a new MyCommand function to queue the command. You can copy/paste one of the others and change the specifics. It’s mostly just boilerplate code copying parameters into the fields of the command.

Finally, add a new case CommandType.MyNewCommand to Execute. Follow the five steps listed above the switch or use the other cases as a pattern.

This is really just wrapper code, so it’s pretty mindless work to expose more Unity API functionality as commands and results. There’s no need to expose the whole Unity API, just the parts you need to use from other threads.

And that’s about all there is to the system. It should be easy to drop into projects and eliminate what seems to be the main reason that Unity programmers don’t use threads. So let’s go put those CPU cores to work!

I am not an expert in threads but i am following you your webposts quite closely because i like the content.

So i shared your post on my facebook and i’ve got a comment from one of my friends as an enquiry of performance tests with this pattern.

Well the main context of his concern was the and if that is the case then the code would be slower if we would use them for each call on the main thread then the code would be slower than wiring it all up in the main thread instead.

Can you please provide an example with some performance tests on that matter? Thanks in advance.

Performance will depend a lot on your Unity API usage. If your code is 100% calls to the Unity API, then there’s no real point in using threads since you’re just adding overhead for the queue. But if your code is 1% calls to the Unity API then the other 99% will benefit from using more CPU cores. In that case the overhead of the queue will be dwarfed by the speed boost you get from the threads. Somewhere in between there’s an equilibrium point where it doesn’t matter.

Exactly how much of your code needs to not be using the Unity API in order for this to be an overall performance win will really depend on the specifics of your code. I can’t think of a good performance test that wouldn’t be terribly contrived, but let me know if you have any ideas for one. I’d venture to say that many games will benefit tremendously from multi-threading as usually there’s a lot of code to do game logic and then a little code to output the result via the Unity API.