General understanding about Multithreading

with Unity

Unity itself has a very strong threading model, basically all C# code is called from a single thread while rendering. Multithreading is heavily abstracted away, for example with the Job System. All Unity functions (especially MonoBehaviours) assume that they are called from the main rendering thread. This is not really unusual, as all GUI applications have a similar concept even in other frameworks.

The difference here is, that unity doesn’t provide a mechanism like run_on_main_thread() { /* code */ } like other frameworks do.

To help with multithreading, the ThreadingHelper class has been built to help with accessing unity objects inside the rest call handler. The ThreadingHelper provides the following functions

  • T ExecuteSync<T>(Func<T> action) - Execute code inside the main/unity thread and return the result
  • void ExecuteAsync(Action action) - Execute code inside the main/unity thread asynchronously
  • void ExecuteAsyncCoroutine(Func<IEnumerator> coroutine) - Execute code inside the main/unity thread as a coroutine

The ThreadingHelper class uses the RestServer class to execute this workload in its update function and therefore inside the main rendering thread. All considerations on running code inside a MonoBehaviour.Update() do apply to code executed with ExecuteSync/Async/Coroutine as well. The RestServer MonoBehaviour must be attached to an enabled GameObject, otherwise ExecuteSync/Async/Coroutine workloads are not executed.

with Request Handlers

Each incoming request spawns a new thread which is then handled by an execution of the handler method provided to the RegisterEndpoint functions.

Why is this important? While implementing it has to be considered, that requests can overlapp each other while executing. For example, imagine that a rest endpoint triggers an animation over many frames inside unity. If the animation request is triggered again before the animation has finished, the animation can be executed twice at the same time. If the animation code doesn’t expect this, weird results can happen.

Sequence diagram to show overlapping animations

The suggestion is, to lock the animation so it can’t be executed again before it finishes. See this code example.

Example ThreadingHelper usage

ExecuteSync

ExecuteSync does three things:

  • Executes the action in the main thread
  • Stops the thread for handling the rest request until the workload is executed on the main rendering thread
  • Returns the result of the calculation back to the request handler

The blocking of the two threads is done with C# WaitHandle mechanism, which should be lightweight on any platform. The request handler will be blocked only for a specific amount of time, until a TimeoutException is raised. The default timeout is 1000ms. You should make sure that the workload can be executed inside this timeframe or increase the timeout by setting ThreadingHelper.Instance.ThreadingMillisecondsTimeout to a higher value. ExecuteSync is executed in the main rendering thread therefore the workload should be lightweight and be executed inside the time limit of a frame. Otherwise, both the request handler and the rendering thread will be blocked until the workload has been executed.

Note Any exceptions thrown inside the ExecuteSync are catched and transported back to the request handler and rethrown.

var position = ThreadingHelper.Instance.ExecuteSync(() => {
    return transform.position;
});

ExecuteAsync

ExecuteAsync is useful to change status on unity objects without interlocking the request handler and the unity rendering thread. This type of execution can’t return any values from inside Unity and after the ExecuteAsync method is called, the request handler immediately continues execution.

ThreadingHelper.Instance.ExecuteAsync(() => {
    transform.position += new Vector3(1.0f, 1.0f, 1.0f);
});

Note Due to the asynchronous nature of this method, exceptions raised in the workload are not propagated to the caller of ExecuteAsync. If the workload doesn’t handle the exception, the global async exception handler will log the exception into unity.

ExecuteCoroutine

ExecuteCoroutine is very similar to the ExecuteAsync method, as it doesn’t interlock the unity rendering thread and the request handler. After calling the method the request handler immediately continues execution. ExecuteCoroutine can be used to have processing spread over multiple frames. See the unity documentation for deeper information about Coroutines.

void Start() {
    server.EndpointCollection.RegisterEndpoint(HttpMethod.GET, "/position", request => {
        ThreadingHelper.Instance.ExecuteAsyncCoroutine(Coroutine);
    });
}

private IEnumerator Coroutine() {
    yield return null;
}