-
Notifications
You must be signed in to change notification settings - Fork 16
Benchmark Awaitable
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.
[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);
}
}
[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);
}
}
[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;
}
}
private static async UnityEngine.Awaitable WaitUntil(Func<bool> condition, CancellationToken cancellationToken = default)
{
while(!condition())
await UnityEngine.Awaitable.EndOfFrameAsync(cancellationToken);
}
- 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(); }
- 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;
}
- 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();
}