Skip to content

Benchmark Awaitable

Aprius edited this page Nov 1, 2024 · 2 revisions

according here

Unity 6 introduces the awaitable type, Awaitable. To put it simply, Awaitable can be considered a subset of UniTask, and in fact, Awaitable's design was influenced by UniTask. It should be able to handle PlayerLoop-based awaits, pooled Tasks, and support for cancellation with CancellationToken in a similar way. With its inclusion in the standard library, you may wonder whether to continue using UniTask or migrate to Awaitable. Here's a brief guide.

First, the functionality provided by Awaitable is equivalent to what coroutines offer. Instead of yield return, you use await; await NextFrameAsync() replaces yield return null; and there are equivalents for WaitForSeconds and EndOfFrame. However, that's the extent of it. Being coroutine-based in terms of functionality, it lacks Task-based features. In practical application development using async/await, operations like WhenAll are essential. Additionally, UniTask enables many frame-based operations (such as DelayFrame) and more flexible PlayerLoopTiming control, which are not available in Awaitable. Of course, there's no Tracker Window either.

Therefore, I recommend using UniTask for application development. UniTask is a superset of Awaitable and includes many essential features. For library development, where you want to avoid external dependencies, using Awaitable as a return type for methods would be appropriate. Awaitable can be converted to UniTask using AsUniTask, so there's no issue in handling Awaitable-based functionality within the UniTask library. Of course, if you don't need to worry about dependencies, using UniTask would be the best choice even for library development.

FromResult

Task

    [Performance]
    [Test]
    public void Test_FromResultTask() { Measure.Method(FromResultTask).MeasurementCount(100).GC().Run(); }

    private async void FromResultTask()
    {
        for (var i = 0; i < 100; i++)
        {
            int _ = await Task.FromResult(42);
        }
    }

image

UniTask

    [Performance]
    [Test]
    public void Test_FromResultUniTask() { Measure.Method(FromResultUniTask).MeasurementCount(100).GC().Run(); }


    public async void FromResultUniTask()
    {
        for (var i = 0; i < 100; i++)
        {
            int _ = await UniTask.FromResult(42);
        }
    }

image

Awaitable

    [Performance]
    [Test]
    public void Test_FromResultAwaitable() { Measure.Method(FromResultAwaitable).MeasurementCount(100).GC().Run(); }

    static readonly AwaitableCompletionSource<int> CompletionSource = new();

    private async void FromResultAwaitable()
    {
        for (var i = 0; i < 100; i++)
        {
            CompletionSource.SetResult(42);
            var awaitable = CompletionSource.Awaitable;
            CompletionSource.Reset();
            int _ = await awaitable;
        }
    }

image

Wait util with Awaitable

private static async UnityEngine.Awaitable WaitUntil(Func<bool> condition, CancellationToken cancellationToken = default)
{
    while(!condition())
        await UnityEngine.Awaitable.EndOfFrameAsync(cancellationToken);
}

2

  • Task
    [Performance]
    [Test]
    public void Test_FromResultTask() { Measure.Method(FromResultTask).MeasurementCount(50).GC().Run(); }

    private Task HeavyJob_Task()
    {
        for (var i = 0; i < 10000; i++)
        {
            _ = Math.Pow(2, 10);
        }

        return Task.CompletedTask;
    }

    private async void FromResultTask() { await HeavyJob_Task(); }

image

  • UniTask
    [Performance]
    [Test]
    public void Test_FromResultUniTask() { Measure.Method(FromResultUniTask).MeasurementCount(50).GC().Run(); }
    
    public async void FromResultUniTask() { await HeavyJob_UniTask(); }
    
    private UniTask HeavyJob_UniTask()
    {
        for (var i = 0; i < 10000; i++)
        {
            _ = Math.Pow(2, 10);
        }

        return UniTask.CompletedTask;
    }

image

  • Awaitable
    [Performance]
    [Test]
    public void Test_FromResultAwaitable() { Measure.Method(FromResultAwaitable).MeasurementCount(50).GC().Run(); }

    private async void FromResultAwaitable() { await HeavyJob_Awaitable(); }

    private async Awaitable HeavyJob_Awaitable()
    {
        //await Awaitable.BackgroundThreadAsync();
        for (var i = 0; i < 10000; i++)
        {
            _ = Math.Pow(2, 10);
        }

        //await Awaitable.MainThreadAsync();
        await Awaitable.EndOfFrameAsync();
    }

image

Clone this wiki locally