Skip to content

MainThreadDispatch

Chillersanim edited this page Feb 8, 2020 · 1 revision

MainThreadDispatch

Namespace: Unity_Tools.Core

Provides functionality releated to dispatching calls to the Unity main thread.
Most Unity API must be called from the main thread, which makes worker threads difficult to implement.
The MainThreadDispatch solves this by providing a bridge between worker threads and the main thread.

public static class MainThreadDispatch

Examples

This example shows how to do raycasting from a worker thread using the MainThreadDispatch.

It creates the NativeArrays used for the RaycastCommand.
It populates the commands array on the main thread using MainThreadDispatch.Invoke.
In the same invocation, it schedules the raycasting command.

Then it awaits completion of the command using MainThreadDispatch.Await.
It passes the handle.IsCompleted to the tester, so that the MainThreadDispatch can regularly check for job completion.
It passes an System.Action to the awaiter which calls handle.Complete, then copies the results to the output and disposes the NativeArrays.

Using the MainThreadDispatch.Invoke for preparation and the MainThreadDispatch.Await for waiting for job completion allows the main thread to continue without having to wait for job completion.
At the same time, all Unity API calls are invoked on the main thread as required.

using System.Collections.Generic;
using System.Threading.Tasks;
using Unity.Collections;
using Unity.Jobs;
using Unity_Tools.Core;
using UnityEngine;

[...]

// Schedules raycasts on the main thread.
public static void DoParallelRaycast(IList<Ray> rays, float maxDistance, LayerMask layermask, List<RaycastHit> output)
{
    // Create native arrays for commands and results
    NativeArray<RaycastCommand> commands = new NativeArray<RaycastCommand>(rays.Count, Allocator.Persistent);
    NativeArray<RaycastHit> results = new NativeArray<RaycastHit>(rays.Count, Allocator.Persistent);

    // Create a raycast job on the main thread
    Task<JobHandle> task = MainThreadDispatch.Invoke(() =>
    {
        // Fill the commands array with the raycast commands
        for (int i = 0; i < rays.Count; i++)
        {
            Ray ray = rays[i];
            commands[i] = new RaycastCommand(ray.origin, ray.direction, maxDistance, layermask);
        }

        // Schedule the raycast
        JobHandle result = RaycastCommand.ScheduleBatch(commands, results, 1);

        // Return the handle
        return result;
    });

    // Wait for the raycast job to complete and populate the output list
    MainThreadDispatch.Await(
        () => task.IsCompleted,   // Determines whether the job has completed
        () => 
        {
            // Make sure the job has completed (Required by Unity)
            JobHandle jobHandle = task.Result;
            jobHandle.Complete();

            // Add the result to the output
            output.AddRange(results);

            // Dispose the native arrays
            results.Dispose();
            commands.Dispose();
        });
}

Remarks

Initialization

You are required to call MainThreadDispatch.Initialize at least once from the main thread before using it in a worker thread.
Calling it multiple times has no performance overhead.
It is good practice to always call it before scheduling a worker thread that uses the MainThreadDispatch.
Failing from doing so will cause exceptions to be thrown when using any method except for MainThreadDispatch.Initialize.

Editor

The main thread dispatch uses the Unity update loop to work of scheduled invocations and awaits.
It does that by using the Unity_Tools.CallProvider class and thus also works in the editor.

Maximum work time

You can control the maximum time spent per frame for working of scheduled workloads by modifying the MainThreadDispatch.MaxWorkTimeMs.
The MainThreadDispatch tries to limit its work time per frame to the defined amount of miliseconds.
Hoewever, it can't cancel an invocation on the main thread, thus long running jobs can cause frame drops anyways.
It is recommended to do as little work as possible in the calls to reduce the performance impact on the main thread.

Properties

  • MaxWorkTimeMs : int
    The maximum amount of work time per frame. Range: [0, inf]

Methods

  • Await(Func<bool>, Action) : void
    Synchronously awaits until the tester returns true and then runs the awaiter (also synchronously).
  • CheckAccess() : bool
    Determines whether the calling thread is the main thread.
  • Initialize() : void
    Call this from the main thread at least once before calling any other method.
  • async Invoke(Action) : Task
    Executes the specified Action synchronously on the main thread.
  • async Invoke(Action, TimeSpan) : Task
    Executes the specified Action synchronously on the main thread or cancels when exceeding the given timeout.
  • async Invoke<T>(Func<T>) : Task<T>
    Executes the specified function synchronously on the main thread and returns the result.
  • async Invoke<T>(Func<T>, TimeSpan) : Task<T>
    Executes the specified function synchronously on the main thread and returns the resultor cancels when exceeding the given timeout.
  • async Invoke(Delegate, params object[]) : Task<object>
    Executes the specified delegate synchronously on the main thread and returns the result.
  • async Invoke(Delegate, TimeSpan, params object[]) : Task<object>
    Executes the specified delegate synchronously on the main thread and returns the result or cancels when exceeding the given timeout.
  • InvokeAsync(Action) : void
    Executes the specified Action asynchronously on the main thread.
  • InvokeAsync(Delegate, params object[]) : void
    Executes the specified delegate asynchronously on the main thread.

Thread safety

MainThreadDispatch is thread safe.