The Awaitable
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 async
and await
pattern, including:
NextFrameAsync
, WaitForSecondsAsync
, EndOfFrameAsync
, FixedUpdateAsync
AsyncOperation
You can use the Awaitable
class with both the await
operator and as an async
return type in your own code, as follows:
async 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
is designed to offer a more efficient alternative to .NET Task
for asynchronous code in Unity projects. The efficiency of Awaitable
comes with some important limitations compared to Task
.
The most significant limitation is that Awaitable
instances are pooled to limit allocations. Consider the following example:
class SomeMonoBehaviorWithAwaitable : MonoBehavior
{
public async void Start()
{
while(true)
{
// do some work on each frame
await Awaitable.NextFrameAsync();
}
}
}
Without pooling, each instance of the MonoBehavior
in this example would allocate an Awaitable
object each frame, increasing garbage collector workload and degrading performance. To mitigate this, Unity returns the Awaitable
object to the internal Awaitable
pool once it’s been awaited.
Important: The pooling of Awaitable
instances means it’s never safe to await
more than once on an Awaitable
instance. Doing so can result in undefined behavior such as an exception or a deadlock.
The .NET ValueTask<TResult>
offers some of the same key benefits and limitations of Awaitable
. The typical recommended use for ValueTask
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.
The following table summarizes the feature comparison between Unity’s Awaitable
class and .NET Task
and ValueTask
:
Feature | Task |
ValueTask |
UnityEngine.Awaitable |
---|---|---|---|
Required allocations |
Many. Allocates on every call to a Task -returning method, increasing memory use and garbage collector workload. |
As-needed. Can be optimized with pooling. |
Minimal as-needed. Calling an Awaitable -returning method usually doesn’t allocate memory, since Awaitable instances are pooled by default. |
Safe to await multiple times | Yes. |
No. Must convert to a Task with ValueTask.AsTask . |
No. Must convert to a Task with custom AsTask extension methods, refer to Awaiting multiple times in the same method in the code examples reference. |
Continuations run asynchronously |
Yes. Using the synchronization context by default, otherwise using the ThreadPool . This increases latency when completing on the main thread in Unity because code must wait until the next frame Update to resume. |
Yes. Optimized for the case where awaited tasks complete synchronously. If they complete asynchronously, the continuation behavior is equivalent to Task . |
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 TaskCompletionSource . |
Not applicable in the typical use case, which is for tasks that mostly complete synchronously. |
Yes. Using AwaitableCompletionSource . |
Can return a value |
Yes. Using Task<TResult> . |
Yes. Using ValueTask<TResult> . |
Yes. Using UnityEngine.Awaitable<T> . |
Built-in support for WaitAll and WaitAny |
Yes. |
No. Must convert to Task with ValueTask.AsTask . |
No. Must convert to a Task with custom AsTask extension methods, refer to Wrapping Awaitable in .NET Task in the code examples reference. |
Unity thread and update loop-aware execution scheduling | No. | No. |
Yes. You can specify which thread an Awaitable resumes on with Awaitable.BackgroundThreadAsync and Awaitable.MainThreadAsync . You can also schedule work relative to the Update or FixedUpdate loops with Awaitable.NextFrameAsync and Awaitable.FixedUpdateAsync . For more information, refer to Awaitable completion and continuation. |
The choice of API depends on the performance profile of your asynchronous code, but in general:
Task
is the only choice when you need to await multiple times or from several consumers concurrently.ValueTask
is a good choice if you have high-throughput asynchronous code that completes synchronously most of the time.Awaitable
is a good choice when:
Awaitable
coroutines are usually more efficient than iterator-based coroutines, especially for cases where the iterator returns non-null values, such as WaitForFixedUpdate
.
However, the performance advantage of Awaitable
coroutines reduces when you run many of them concurrently. For example, a MonoBehaviour such as the one in the previous code example, which awaits Awaitable.NextFrameAsync
in a while
loop, is likely to cause performance problems if attached to every GameObjectThe fundamental object in Unity scenes, which can represent characters, props, scenery, cameras, waypoints, and more. A GameObject’s functionality is defined by the Components attached to it. More info
See in Glossary in a large project.