UniTask async/await patterns for Unity including cancellation, lifecycle binding, and coroutine interop.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add IdoCohen560/claude-unity-game-studio --skill tools-unity-unitask --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Tools Unity Unitask?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/idocohen560-tools-unity-unitask-claude-unity-game-studio)More formats (shields.io, HTML) on the badges page.
---
name: tools-unity-unitask
description: UniTask async/await patterns for Unity including cancellation, lifecycle binding, and coroutine interop.
---
# UniTask Async/Await for Unity
## Overview
UniTask provides efficient async/await support for Unity with zero allocation, proper cancellation, and Unity lifecycle integration.
## When to Use
- Async operations in Unity (loading, networking, delays)
- Replacing coroutines with async/await
- Managing cancellation in MonoBehaviours
- Async initialization patterns
- Parallel async operations
## Installation
```json
// manifest.json
"com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask"
```
## Basic Patterns
### Simple Async Method
```csharp
public async UniTask LoadGameAsync()
{
await UniTask.Delay(1000); // 1 second delay
await LoadAssetsAsync();
await InitializeSystemsAsync();
}
```
### Async with Return Value
```csharp
public async UniTask<PlayerData> LoadPlayerAsync()
{
var json = await File.ReadAllTextAsync(path);
return JsonUtility.FromJson<PlayerData>(json);
}
```
### Fire-and-Forget (Use Carefully!)
```csharp
// ForgetTask() suppresses warnings but loses error handling
LoadGameAsync().Forget();
// Better: Use UniTaskVoid for true fire-and-forget
public async UniTaskVoid StartBackgroundTask()
{
try
{
await DoWorkAsync();
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
```
## Cancellation Patterns
### Basic CancellationToken
```csharp
public class GameLoader : MonoBehaviour
{
private CancellationTokenSource _cts;
private void OnEnable()
{
_cts = new CancellationTokenSource();
LoadAsync(_cts.Token).Forget();
}
private void OnDisable()
{
_cts?.Cancel();
_cts?.Dispose();
_cts = null;
}
private async UniTask LoadAsync(CancellationToken ct)
{
await UniTask.Delay(1000, cancellationToken: ct);
// Work here...
}
}
```
### Destroy CancellationToken (Preferred for MonoBehaviour)
```csharp
public class Enemy : MonoBehaviour
{
private async UniTaskVoid Start()
{
// Automatically cancelled when GameObject destroyed
var ct = this.GetCancellationTokenOnDestroy();
while (!ct.IsCancellationRequested)
{
await UniTask.Delay(1000, cancellationToken: ct);
Patrol();
}
}
}
```
### Linked Cancellation
```csharp
public async UniTask DoWorkAsync(CancellationToken externalCt)
{
// Link external token with destroy token
var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
externalCt,
this.GetCancellationTokenOnDestroy()
);
try
{
await LongRunningTask(linkedCts.Token);
}
finally
{
linkedCts.Dispose();
}
}
```
### Timeout
```csharp
// Timeout with exception
await task.Timeout(TimeSpan.FromSeconds(5));
// Timeout without exception
var (hasValue, result) = await task.TimeoutWithoutException(TimeSpan.FromSeconds(5));
if (!hasValue)
{
Debug.Log("Operation timed out");
}
// Timeout with CancellationTokenSource
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
await task.AttachExternalCancellation(cts.Token);
```
## Unity Lifecycle Integration
### Waiting for Frames
```csharp
// Wait one frame
await UniTask.Yield();
// Wait for end of frame
await UniTask.WaitForEndOfFrame();
// Wait for fixed update
await UniTask.WaitForFixedUpdate();
// Wait multiple frames
await UniTask.DelayFrame(10);
// Wait for specific player loop
await UniTask.Yield(PlayerLoopTiming.PreUpdate);
```
### Player Loop Timing Options
```csharp
// Available timings:
PlayerLoopTiming.Initialization
PlayerLoopTiming.EarlyUpdate
PlayerLoopTiming.FixedUpdate
PlayerLoopTiming.PreUpdate
PlayerLoopTiming.Update
PlayerLoopTiming.PreLateUpdate
PlayerLoopTiming.PostLateUpdate
PlayerLoopTiming.TimeUpdate
```
### Waiting for Conditions
```csharp
// Wait until condition is true
await UniTask.WaitUntil(() => player.IsReady);
// Wait while condition is true
await UniTask.WaitWhile(() => isLoading);
// Wait until with cancellation
await UniTask.WaitUntil(
() => player.IsReady,
cancellationToken: ct
);
// Wait until with timeout
await UniTask.WaitUntil(() => player.IsReady)
.Timeout(TimeSpan.FromSeconds(10));
```
### Waiting for Unity Events
```csharp
// Wait for button click
await button.OnClickAsync();
// Wait for trigger enter
var other = await gameObject.OnTriggerEnterAsync();
// Wait for collision
var collision = await gameObject.OnCollisionEnterAsync();
// Wait for animation event
await animator.WaitForAnimationEvent("AttackHit");
```
## Parallel Operations
### WhenAll (Wait for All)
```csharp
// Wait for all tasks to complete
var results = await UniTask.WhenAll(
LoadTexturesAsync(),
LoadAudioAsync(),
LoadConfigAsync()
);
// With different return types
var (textures, audio, config) = await UniTask.WhenAll(
LoadTexturesAsync(),
LoadAudioAsync(),
LoadConfigAsync()
);
```
### WhenAny (Wait for First)
```csharp
// Wait for first to complete
var (winIndex, result1, result2) = await UniTask.WhenAny(
TryServerAAsync(),
TryServerBAsync()
);
// Use winner
if (winIndex == 0)
{
UseResult(result1);
}
```
### Throttling Parallel Operations
```csharp
// Process with limited concurrency
var semaphore = new SemaphoreSlim(3); // Max 3 concurrent
await UniTask.WhenAll(items.Select(async item =>
{
await semaphore.WaitAsync();
try
{
await ProcessItemAsync(item);
}
finally
{
semaphore.Release();
}
}));
```
## Coroutine Interop
### Convert Coroutine to UniTask
```csharp
// Wrap existing coroutine
await MyCoroutine().ToUniTask();
// With cancellation
await MyCoroutine().ToUniTask(cancellationToken: ct);
// From IEnumerator
IEnumerator LegacyCoroutine()
{
yield return new WaitForSeconds(1);
}
await LegacyCoroutine().ToUniTask();
```
### Convert UniTask to Coroutine
```csharp
// For APIs that require coroutines
StartCoroutine(MyUniTask().ToCoroutine());
```
### Async Trigger Components
```csharp
// Add async triggers to GameObjects
var trigger = gameObject.GetAsyncTriggerEnterTrigger();
// Wait for trigger
var other = await trigger.OnTriggerEnterAsync();
```
## Resource Loading
### Addressables Integration
```csharp
public async UniTask<T> LoadAssetAsync<T>(string address, CancellationToken ct)
{
var handle = Addressables.LoadAssetAsync<T>(address);
try
{
return await handle.ToUniTask(cancellationToken: ct);
}
catch (OperationCanceledException)
{
Addressables.Release(handle);
throw;
}
}
```
### Scene Loading
```csharp
public async UniTask LoadSceneAsync(string sceneName, CancellationToken ct)
{
await SceneManager.LoadSceneAsync(sceneName)
.ToUniTask(cancellationToken: ct);
}
// With progress
public async UniTask LoadSceneWithProgressAsync(string sceneName, IProgress<float> progress)
{
await SceneManager.LoadSceneAsync(sceneName)
.ToUniTask(progress: progress);
}
```
### Asset Bundle Loading
```csharp
public async UniTask<AssetBundle> LoadBundleAsync(string url, CancellationToken ct)
{
var request = UnityWebRequestAssetBundle.GetAssetBundle(url);
await request.SendWebRequest().ToUniTask(cancellationToken: ct);
if (request.result != UnityWebRequest.Result.Success)
{
throw new Exception(request.error);
}
return DownloadHandlerAssetBundle.GetContent(request);
}
```
## Error Handling
### Try-Catch Pattern
```csharp
public async UniTask SafeLoadAsync()
{
try
{
await LoadDataAsync();
}
catch (OperationCanceledException)
{
// Expected when cancelled - don't log as error
Debug.Log("Load cancelled");
}
catch (Exception ex)
{
Debug.LogException(ex);
ShowErrorUI();
}
}
```
### SuppressCancellationThrow
```csharp
// Returns (isCancelled, result) instead of throwing
var (cancelled, result) = await LoadAsync()
.SuppressCancellationThrow();
if (cancelled)
{
return; // Graceful exit
}
ProcessResult(result);
```
### Exception Handling in WhenAll
```csharp
// Collect all exceptions
try
{
await UniTask.WhenAll(tasks);
}
catch (AggregateException ae)
{
foreach (var ex in ae.InnerExceptions)
{
Debug.LogException(ex);
}
}
```
## Progress Reporting
### IProgress<float>
```csharp
public async UniTask LoadWithProgressAsync(IProgress<float> progress, CancellationToken ct)
{
var items = await GetItemsAsync();
for (int i = 0; i < items.Count; i++)
{
await ProcessItemAsync(items[i], ct);
progress?.Report((float)(i + 1) / items.Count);
}
}
// Usage
var progress = new Progress<float>(p => loadingBar.value = p);
await LoadWithProgressAsync(progress, ct);
```
### Custom Progress
```csharp
public struct LoadProgress
{
public string CurrentItem;
public int Loaded;
public int Total;
public float Percent => (float)Loaded / Total;
}
public async UniTask LoadAsync(IProgress<LoadProgress> progress)
{
var items = await GetItemsAsync();
for (int i = 0; i < items.Count; i++)
{
progress?.Report(new LoadProgress
{
CurrentItem = items[i].Name,
Loaded = i + 1,
Total = items.Count
});
await ProcessItemAsync(items[i]);
}
}
```
## UniTask vs Task
### When to Use UniTask
```csharp
// Unity main thread operations
await UniTask.Delay(1000); // Use UniTask
await UniTask.Yield(); // Use UniTask
await SceneManager.LoadSceneAsync(s); // Use UniTask
// Fire and forget in Unity
DoWorkAsync().Forget(); // UniTask only
```
### When to Use Task
```csharp
// Pure .NET operations (no Unity API)
await File.ReadAllTextAsync(path); // Task is fine
await Task.Run(() => HeavyComputation()); // Task for thread pool
// Converting
var result = await task.AsUniTask(); // Task to UniTask
var task = unitask.AsTask(); // UniTask to Task
```
## Common Pitfalls
### Pitfall 1: Missing CancellationToken
```csharp
// BAD: No cancellation, leaks when object destroyed
public async UniTaskVoid Start()
{
await UniTask.Delay(10000);
DoSomething(); // May crash if destroyed
}
// GOOD: Use destroy token
public async UniTaskVoid Start()
{
await UniTask.Delay(10000, cancellationToken: destroyCancellationToken);
DoSomething();
}
```
### Pitfall 2: Forget Without Error Handling
```csharp
// BAD: Exceptions silently swallowed
DoWorkAsync().Forget();
// GOOD: Handle errors
async UniTaskVoid DoWorkSafe()
{
try
{
await DoWorkAsync();
}
catch (Exception ex) when (!(ex is OperationCanceledException))
{
Debug.LogException(ex);
}
}
DoWorkSafe().Forget();
```
### Pitfall 3: Blocking on Main Thread
```csharp
// BAD: Blocks main thread
var result = LoadAsync().GetAwaiter().GetResult();
// GOOD: Await properly
var result = await LoadAsync();
```
### Pitfall 4: Unnecessary Allocations
```csharp
// BAD: Allocates closure
await UniTask.Delay(1000).ContinueWith(_ => DoSomething());
// GOOD: Just await
await UniTask.Delay(1000);
DoSomething();
```
## Best Practices
1. **Always use CancellationToken** for MonoBehaviour async methods
2. **Use GetCancellationTokenOnDestroy()** for automatic cleanup
3. **Handle OperationCanceledException** separately from other exceptions
4. **Prefer UniTask over Task** for Unity operations
5. **Use WhenAll** for parallel operations
6. **Report progress** for long-running operations
7. **Use SuppressCancellationThrow** for optional cancellation handling
8. **Avoid Forget()** without proper error handling
## Performance Tips
- UniTask is struct-based (zero allocation when awaited directly)
- Avoid unnecessary `.AsTask()` conversions
- Use `UniTask.Yield()` instead of `await UniTask.Delay(0)`
- Pool `CancellationTokenSource` for high-frequency operations
- Use `UniTaskCompletionSource` for custom async patterns
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!