UniTask: An Efficient Async/Await Solution for Unity
UniTask offers developers a unique approach to handling asynchronous operations within the Unity environment. By providing a lightweight, allocation-free way to integrate async/await functionality, UniTask stands out as a practical tool for optimizing performance in Unity projects. Here, we delve into various facets of UniTask to understand its benefits and how it can enhance Unity development.
Key Features of UniTask
- Zero Allocation Asynchronicity: UniTask is structured around
UniTask<T>
and utilizes a custom AsyncMethodBuilder to ensure operations incur zero memory allocation, making it highly efficient. - Awaitable Unity Elements: UniTask enables all Unity AsyncOperations and Coroutine methods to be awaitable, simplifying asynchronous workflows.
- PlayerLoop-Based Tasks: Tasks like
UniTask.Yield
,UniTask.Delay
, and others, make it possible to replace coroutine operations seamlessly. - Awaitable Events: Events from MonoBehaviour and uGUI can be treated as awaitable, providing more control and flexibility.
- Thread-Free Execution: UniTask operates entirely on Unity's PlayerLoop, forgoing the need for threads, which is especially beneficial for platforms like WebGL.
- Asynchronous LINQ and More: It supports asynchronous LINQ queries, integrates with Channels, and offers AsyncReactiveProperty for reactive programming needs.
- Task Management: A TaskTracker window assists in preventing memory leaks by monitoring task allocations.
- Compatibility: UniTask maintains compatibility with Task, ValueTask, and IValueTaskSource behaviors, ensuring seamless integration.
Installation and Setup
To get started with UniTask, developers can install it through the UPM package using a git reference or by downloading the asset package from UniTask releases. Including the namespace using Cysharp.Threading.Tasks;
is essential to access extension methods and features.
Basic Usage
Utilize UniTask as an alternative to the traditional Task
async UniTask<string> DemoAsync()
{
var asset = await Resources.LoadAsync<TextAsset>("foo"); // Await Unity's AsyncOperation
var txt = await UnityWebRequest.Get("https://...").SendWebRequest();
await SceneManager.LoadSceneAsync("scene2");
var updatedAsset = await Resources.LoadAsync<TextAsset>("bar").WithCancellation(this.GetCancellationTokenOnDestroy());
await UniTask.Delay(TimeSpan.FromSeconds(10), ignoreTimeScale: false);
return (asset as TextAsset)?.text ?? throw new InvalidOperationException("Asset not found");
}
Cancellation and Exception Handling
UniTask supports robust cancellation via CancellationTokenSource
. Here's an example of handling cancellation:
var cts = new CancellationTokenSource();
cancelButton.onClick.AddListener(() => cts.Cancel());
await UnityWebRequest.Get("http://google.com").SendWebRequest().WithCancellation(cts.Token);
Exception handling in UniTask relies on catching exceptions like OperationCanceledException
and others, with specific guidelines for propagating cancellations efficiently.
Enhancing Unity Workflows
UniTask simplifies managing asynchronous operations in Unity with methods like UniTask.WhenAll
, UniTask.WhenAny
, and UniTask.WhenEach
, enabling smooth concurrent operations and richer asynchronous interactions.
For instance, loading multiple resources asynchronously in parallel is straightforward and efficient:
public async UniTaskVoid LoadManyAsync()
{
var (sprite1, sprite2, sprite3) = await UniTask.WhenAll(
LoadResource("Image1"),
LoadResource("Image2"),
LoadResource("Image3"));
}
async UniTask<Sprite> LoadResource(string path)
{
var resourceRequest = await Resources.LoadAsync<Sprite>(path);
return resourceRequest as Sprite;
}
Conclusion
UniTask enhances Unity development by supporting efficient, allocation-free, asynchronous programming, reducing system overheads, and improving execution speed. By matching Unity's single-threaded design and offering support for common tasks such as cancellation and progress tracking, UniTask is a valuable addition to any Unity developer's toolkit.