UniTask Documentation

repository·master·Indexed 27 days ago

https://github.com/cysharp/unitask

An efficient, allocation-free async/await integration for Unity designed to replace coroutines and optimize asynchronous operations within the Unity PlayerLoop. UniTask provides a lightweight alternative to Task<T> for Unity's single-threaded engine, allowing developers to await native Unity objects such as AsyncOperation, ResourceRequest, and UnityWebRequestAsyncOperation. It includes features for cancellation handling via CancellationToken, concurrent operations with WhenAll, thread switching, and specialized PlayerLoopTiming control.

Tokens
14.5K
Snippets
42
Records
67
Agent score
95%

What's inside UniTask

  1. Overview of UniTask

    master

    UniTask is a high-performance, zero-allocation async/await solution designed specifically for Unity. It utilizes value-type UniTask<T> and a custom AsyncMethodBuilder to eliminate heap allocations during asynchronous operations.

    Key features include:

    • Unity Integration: Makes all Unity AsyncOperations and Coroutines awaitable.
    • Coroutine Replacement: Provides PlayerLoop-based tasks (e.g., UniTask.Yield, UniTask.Delay, UniTask.DelayFrame) that can replace standard coroutines.
    • Event Support: Provides awaitable/async extensions for MonoBehaviour message events and uGUI events.
    • Platform Compatibility: Runs entirely on Unity's PlayerLoop without using threads, making it compatible with WebGL and WASM.
    • Async LINQ: Includes Channel and AsyncReactiveProperty for asynchronous LINQ operations.
    • Memory Management: Includes a TaskTracker EditorWindow to track UniTask allocations and prevent memory leaks.
    • Standard Compatibility: Highly compatible with native Task, ValueTask, and IValueTaskSource.
  2. Implement Timeouts with CancelAfterSlim and TimeoutController

    master

    Timeouts in UniTask can be implemented in several ways:

    1. CancellationTokenSource.CancelAfterSlim(TimeSpan): Use this instead of the standard CancelAfter to avoid dependency on threading timers; it uses the Unity PlayerLoop instead.
    2. Linked Tokens: Use CancellationTokenSource.CreateLinkedTokenSource to combine a timeout token with other cancellation sources (e.g., a UI button click).
    3. TimeoutController: For high-performance scenarios where you want to reduce CancellationTokenSource allocations, reuse a TimeoutController. Call Reset() on the controller when the operation succeeds to prepare it for reuse.

    Warning: Avoid using .Timeout() or .TimeoutWithoutException() extension methods if possible. These work externally to the task and cannot stop the running task; instead, pass a CancellationToken to the method so the cancellation acts from inside the task.

    // Using CancelAfterSlim
    var cts = new CancellationTokenSource();
    cts.CancelAfterSlim(TimeSpan.FromSeconds(5));
    
    try
    {
        await UnityWebRequest.Get("http://foo").SendWebRequest().WithCancellation(cts.Token);
    }
    catch (OperationCanceledException ex) when (ex.CancellationToken == cts.Token)
    {
        UnityEngine.Debug.Log("Timeout");
    }
    
    // Using TimeoutController for low allocation
    TimeoutController timeoutController = new TimeoutController();
    
    async UniTask FooAsync()
    {
        try
        {
            await UnityWebRequest.Get("http://foo").SendWebRequest()
                .WithCancellation(timeoutController.Timeout(TimeSpan.FromSeconds(5)));
            timeoutController.Reset(); 
        }
        catch (OperationCanceledException ex)
        {
            if (timeoutController.IsTimeout())
            {
                UnityEngine.Debug.Log("timeout");
            }
        }
    }
  3. Configure Immediate Cancellation in UniTask

    master

    By default, UniTask methods that use the Unity PlayerLoop (like UniTask.Yield or UniTask.Delay) check the CancellationToken state during the player loop, meaning cancellation might not be immediate.

    To force immediate cancellation, set the cancelImmediately flag to true. Note that this is more computationally expensive because it uses CancellationToken.Register instead of checking the token on the player loop.

    await UniTask.Yield(cancellationToken, cancelImmediately: true);
  4. Handle Cancellation and Exceptions in UniTask

    master

    When cancellation is detected, UniTask methods throw OperationCanceledException.

    • Manual Cancellation: To cancel an async method manually, throw OperationCanceledException.
    • Global Exception Handling: Unhandled exceptions (other than OperationCanceledException) are propagated to UniTaskScheduler.UnobservedTaskException. You can customize the behavior by setting an action to this property or change the log level via UniTaskScheduler.UnobservedExceptionWriteLogType.
    • Exception Filtering: If you catch an exception but want OperationCanceledException to propagate to global cancellation handling, use an exception filter (when (!(ex is OperationCanceledException))).
    • Performance Optimization: To avoid the overhead of throwing OperationCanceledException, use UniTask.SuppressCancellationThrow(). This returns a tuple (bool IsCanceled, T Result) instead of throwing.
    // Manual cancellation
    public async UniTask<int> FooAsync()
    {
        await UniTask.Yield();
        throw new OperationCanceledException();
    }
    
    // Using exception filters to allow cancellation to propagate
    public async UniTask<int> BarAsync()
    {
        try
        {
            var x = await FooAsync();
            return x * 2;
        }
        catch (Exception ex) when (!(ex is OperationCanceledException))
        {
            return -1;
        }
    }
    
    // High-performance cancellation check without throwing
    var (isCanceled, _) = await UniTask.DelayFrame(10, cancellationToken: cts.Token).SuppressCancellationThrow();
    if (isCanceled)
    {
        // ...
    }
  5. Manage Cancellation with CancellationToken

    master

    Cancellation is handled via standard CancellationToken objects.

    Best Practices:

    • Use this.GetCancellationTokenOnDestroy() (an extension method for MonoBehaviour) to link a task's lifetime to a GameObject's lifetime.
    • For manual control, use CancellationTokenSource.
    • For chained cancellation, pass the CancellationToken through all asynchronous method calls in the chain.

    Usage Examples:

    • Passing to factory methods: UniTask.DelayFrame(1000, cancellationToken: cts.Token);
    • Passing to extension methods: UnityWebRequest.Get("...").SendWebRequest().WithCancellation(cts.Token);
    // Link to MonoBehaviour lifetime
    await UniTask.DelayFrame(1000, cancellationToken: this.GetCancellationTokenOnDestroy());
    
    // Manual cancellation
    var cts = new CancellationTokenSource();
    // ... trigger cts.Cancel() ...
    await UnityWebRequest.Get("http://google.co.jp").SendWebRequest().WithCancellation(cts.Token);
    
    // Chained cancellation pattern
    async UniTask FooAsync(CancellationToken cancellationToken)
    {
        await BarAsync(cancellationToken);
    }
    
    async UniTask BarAsync(CancellationToken cancellationToken)
    {
        await UniTask.Delay(TimeSpan.FromSeconds(3), cancellationToken);
    }
  6. Use Async LINQ with UniTaskAsyncEnumerable

    master

    UniTask provides asynchronous LINQ capabilities via UniTaskAsyncEnumerable. To use them, include using Cysharp.Threading.Tasks.Linq;.

    Key Concepts:

    • Pull-based: Unlike UniRx (push-based), UniTaskAsyncEnumerable is a pull-based asynchronous stream.
    • Generators: Includes Unity-specific generators like EveryUpdate, Timer, Interval, and EveryValueChanged.
    • Async Operators: For using async methods inside LINQ operators, use the ***Await or ***AwaitWithCancellation overloads (e.g., SelectAwait).

    Usage Examples:

    • await foreach (C# 8.0+)
    • .ForEachAsync() (C# 7.3+)
    • .Subscribe() (for fire-and-forget style)
    // Unity 2020.2, C# 8.0
    await foreach (var _ in UniTaskAsyncEnumerable.EveryUpdate().WithCancellation(token))
    {
        Debug.Log("Update() " + Time.frameCount);
    }
    
    // C# 7.3(Unity 2018.3~)
    await UniTaskAsyncEnumerable.EveryUpdate().ForEachAsync(_ =>
    {
        Debug.Log("Update() " + Time.frameCount);
    }, token);
    
    // Async LINQ example
    await okButton.OnClickAsAsyncEnumerable().Where((x, i) => i % 2 == 0).ForEachAsync(_ =>
    {
    });
  7. Report Progress with IProgress

    master

    To track progress in asynchronous operations, use the ToUniTask(IProgress<float> progress = null, ...) extension method.

    Best Practice: Avoid using new System.Progress<T> as it causes heap allocations. Instead, use Cysharp.Threading.Tasks.Progress or implement the IProgress<float> interface directly on your class to minimize allocations.

    public class Foo : MonoBehaviour, IProgress<float>
    {
        public void Report(float value)
        {
            UnityEngine.Debug.Log(value);
        }
    
        public async UniTaskVoid WebRequest()
        {
            var request = await UnityWebRequest.Get("http://google.co.jp")
                .SendWebRequest()
                .ToUniTask(progress: this);
        }
    }
  8. Replace UnitySynchronizationContext with UniTaskSynchronizationContext

    master

    Unity's default UnitySynchronizationContext can be a performance bottleneck. You can replace it with UniTaskSynchronizationContext to improve performance.

    Warning: This is an optional choice and not always recommended. UniTaskSynchronizationContext is less performant than using async UniTask directly, is not a complete replacement for the standard synchronization context, and does not guarantee full behavioral compatibility with UnitySynchronizationContext.

    public class SyncContextInjecter
    {
        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
        public static void Inject()
        {
            SynchronizationContext.SetSynchronizationContext(new UniTaskSynchronizationContext());
        }
    }
  9. Re-initialize UniTask PlayerLoop after ECS injection

    master
    If you use Unity's Entities package, it may reset the custom PlayerLoop to default values during BeforeSceneLoad, which can break UniTask. To fix this, re-initialize the UniTask PlayerLoop after ECS has finished its injection by retrieving the current loop and passing it to PlayerLoopHelper.Initialize.
  10. Configure UniTask Thread Pool usage

    master

    Most UniTask methods run on a single thread (PlayerLoop). Only UniTask.RunOnThreadPool and UniTask.SwitchToThreadPool run on the thread pool.

    Warning: Using the thread pool may cause incompatibility with platforms like WebGL. Also, UniTask.Run is deprecated; use UniTask.RunOnThreadPool instead.

  11. Report Progress with UniTask

    master

    Many Unity async operations provide ToUniTask(IProgress<float> progress = null, ...) extension methods.

    Best Practices:

    • Avoid System.Progress<T>: It causes allocations every time. Use Cysharp.Threading.Tasks.Progress instead.
    • Use Progress.Create: For simple lambda-based reporting.
    • Use Progress.CreateOnlyValueChanged: To only trigger the callback when the progress value actually changes.
    • Implement IProgress<float>: For the best performance (zero lambda allocation), implement the interface directly on your class.
    // Using Progress factory
    var progress = Progress.Create<float>(x => Debug.Log(x));
    var request = await UnityWebRequest.Get("http://google.co.jp").SendWebRequest().ToUniTask(progress: progress);
    
    // Implementing IProgress for zero allocation
    public class Foo : MonoBehaviour, IProgress<float>
    {
        public void Report(float value) => UnityEngine.Debug.Log(value);
    
        public async UniTaskVoid WebRequest()
        {
            var request = await UnityWebRequest.Get("http://google.co.jp")
                .SendWebRequest()
                .ToUniTask(progress: this);
        }
    }