Introduction to asynchronous programming with Awaitable
Understand the key features of Unity's Awaitable and how it compares to both .NET Task and iterator-based coroutines.
Read time 5 minutesLast updated 14 days ago
The class is a custom Unity type that can be awaited and used as an async return type in the C# asynchronous programming model. Most of Unity's asynchronous APIs support the and pattern, including:
Awaitableasyncawait- Unity coroutines: ,
NextFrameAsync,WaitForSecondsAsync,EndOfFrameAsyncFixedUpdateAsync - Switching to Background Thread or Main Thread
- All types inheriting from
AsyncOperation - Unity Events
- Async GPU Readback
You can use the class with both the operator and as an return type in your own code, as follows:
Awaitableawaitasyncasync Awaitable<List<Achievement>> GetAchievementsAsync(){ var apiResult = await SomeMethodReturningATask(); // or any await-compatible type List<Achievement> achievements = JsonConvert.DeserializeObject<List<Achievement>>(apiResult); return achievements;}async Awaitable ShowAchievementsView(){ ShowLoadingOverlay(); List<Achievement> achievements = await GetAchievementsAsync(); HideLoadingOverlay(); ShowAchivementsList(achievements);}
Awaitable compared to .NET Task
AwaitableTaskAwaitableTaskThe most significant limitation is that instances are pooled to limit allocations. Consider the following example:
Awaitableclass SomeMonoBehaviorWithAwaitable : MonoBehaviour{ public async void Start() { while(true) { // do some work on each frame await Awaitable.NextFrameAsync(); } }}
Without pooling, each instance of the in this example would allocate an object each frame, increasing garbage collector workload and degrading performance. To mitigate this, Unity returns the object to the internal pool once it's been awaited.
MonoBehaviourAwaitableAwaitableAwaitableAwaitable compared to .NET ValueTask
The .NET offers some of the same key benefits and limitations of . The typical recommended use for is for asynchronous workloads that are expected to complete synchronously most of the time. For more information, refer to Understanding the Whys, Whats, and Whens of ValueTask.
ValueTask<TResult>AwaitableValueTaskAwaitable, Task, and ValueTask summary
The following table summarizes the feature comparison between Unity's class and .NET and :
AwaitableTaskValueTaskFeature |
|
|
|
|---|---|---|---|
| Required allocations | Many. Allocates on every call to a | As-needed. Can be optimized with pooling. | Minimal as-needed. Calling an |
| Safe to await multiple times | Yes. | No. Must convert to a | No. Must convert to a |
| Continuations run asynchronously | Yes. Using the synchronization context by default, otherwise using the | Yes. Optimized for the case where awaited tasks complete synchronously. If they complete asynchronously, the continuation behavior is equivalent to | No. Continuation runs synchronously when completion is triggered, meaning code resumes immediately in the same frame in which completion is triggered. Refer to Awaitable completion and continuation for more information. |
| Completion can be triggered by code | Yes. Using | Not applicable in the typical use case, which is for tasks that mostly complete synchronously. | Yes. Using |
| Can return a value | Yes. Using | Yes. Using | Yes. Using |
Built-in support for | Yes. | No. Must convert to | No. Must convert to a |
| Unity thread and update loop-aware execution scheduling | No. | No. | Yes. You can specify which thread an |
When to use Awaitable over Task or ValueTask
The choice of API depends on the performance profile of your asynchronous code, but in general:
-
is the only choice when you need to await multiple times or from several consumers concurrently.
Task -
is a good choice if you have high-throughput asynchronous code that completes synchronously most of the time.
ValueTask -
is a good choice when:
Awaitable- You don't need to await your methods multiple times and expect them to mostly complete asynchronously.
- You want your asynchronous tasks to have built-in support for Unity-specific concepts like the main thread and the Update and FixedUpdate loops.
Awaitable compared to iterator-based coroutines
AwaitableWaitForFixedUpdateHowever, the performance advantage of coroutines reduces when you run many of them concurrently. For example, a MonoBehaviour such as the one in the previous code example, which awaits in a loop, is likely to cause performance problems if attached to every GameObject in a large project.
AwaitableAwaitable.NextFrameAsyncwhile