Skip to content
Back to skills

Unitask

ASecurity

UniTask async/await for Unity — zero-alloc async, cancellation tokens, PlayerLoop integration, async LINQ. Use instead of coroutines for cancellation support and cleaner async code.

  • 21 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added May 28, 2026
ai-agentsgoapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned May 28, 2026

npx -y skills add XeldarAlz/everything-claude-unity --skill unitask --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Unitask?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Unitask
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/xeldaralz-unitask/badge)](https://www.skillsdirectory.com/skills/xeldaralz-unitask)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: unitask
description: "UniTask async/await for Unity — zero-alloc async, cancellation tokens, PlayerLoop integration, async LINQ. Use instead of coroutines for cancellation support and cleaner async code."
globs: ["**/UniTask*", "**/*Async*.cs", "**/Cysharp*"]
---

# UniTask — Zero-Allocation Async/Await for Unity

UniTask (Cysharp) provides async/await that integrates natively with Unity's PlayerLoop, produces zero GC allocations, and supports proper cancellation. Prefer UniTask over coroutines and `System.Threading.Tasks.Task` in all Unity projects.

## UniTask vs Coroutines vs System.Threading.Tasks.Task

| Feature | Coroutine | Task | UniTask |
|---------|-----------|------|---------|
| GC allocation | Enumerator + box | Task object + state machine | Zero (struct-based) |
| Cancellation | Manual flag | CancellationToken | CancellationToken |
| Return values | No | Yes | Yes |
| Exception handling | Swallowed silently | try/catch | try/catch |
| Runs on thread pool | No | Yes (dangerous in Unity) | No (PlayerLoop) |
| Awaitable | No | Yes | Yes |

## Basic Usage

### Method Signatures

```csharp
using Cysharp.Threading.Tasks;

// Awaitable, returns nothing
public async UniTask LoadLevelAsync(CancellationToken ct)
{
    await UniTask.Delay(1000, cancellationToken: ct);
}

// Awaitable, returns a value
public async UniTask<int> CalculateScoreAsync(CancellationToken ct)
{
    await UniTask.Yield(ct);
    return 100;
}

// Fire-and-forget (use sparingly, only at call boundaries)
public async UniTaskVoid OnButtonClickedAsync()
{
    await DoSomethingAsync(this.GetCancellationTokenOnDestroy());
}
```

### CRITICAL: Never Use async void

```csharp
// BAD — exceptions silently swallowed, no cancellation, GC allocation
public async void DoSomething() { ... }

// GOOD — proper error propagation, zero alloc
public async UniTask DoSomethingAsync(CancellationToken ct) { ... }

// GOOD — fire-and-forget with error logging
public async UniTaskVoid DoSomethingFireAndForget() { ... }
```

## Waiting and Delays

```csharp
// Time-based delays
await UniTask.Delay(1000, cancellationToken: ct);                    // Milliseconds
await UniTask.Delay(TimeSpan.FromSeconds(1.5f), cancellationToken: ct);

// Frame-based waits
await UniTask.Yield();                                                // Next frame
await UniTask.Yield(PlayerLoopTiming.FixedUpdate);                   // Next FixedUpdate
await UniTask.NextFrame(ct);                                          // Explicit next frame
await UniTask.DelayFrame(5, cancellationToken: ct);                  // Wait N frames

// Condition waits
await UniTask.WaitUntil(() => _isReady, cancellationToken: ct);
await UniTask.WaitWhile(() => _isLoading, cancellationToken: ct);
await UniTask.WaitUntilValueChanged(transform, t => t.position, cancellationToken: ct);

// Unity async operation wrappers
await SceneManager.LoadSceneAsync("GameScene").ToUniTask(cancellationToken: ct);
await Resources.LoadAsync<Texture2D>("myTexture").ToUniTask(cancellationToken: ct);
await UnityWebRequest.Get(url).SendWebRequest().ToUniTask(cancellationToken: ct);
```

## Cancellation Tokens

### CRITICAL: Always Pass Cancellation Tokens

Async operations that outlive their owning object cause `MissingReferenceException` and undefined behavior. Every async method must accept and respect a `CancellationToken`.

### Pattern 1: GetCancellationTokenOnDestroy (Simple)

```csharp
public class SimpleAsync : MonoBehaviour
{
    private async UniTaskVoid Start()
    {
        // Token auto-cancels when this MonoBehaviour is destroyed
        CancellationToken ct = this.GetCancellationTokenOnDestroy();

        await UniTask.Delay(2000, cancellationToken: ct);
        Debug.Log("This won't run if object was destroyed");
    }
}
```

### Pattern 2: Manual CancellationTokenSource (Enable/Disable)

```csharp
public class ManagedAsync : MonoBehaviour
{
    private CancellationTokenSource _cts;

    private void OnEnable()
    {
        _cts = new CancellationTokenSource();
        RunLoopAsync(_cts.Token).Forget();
    }

    private void OnDisable()
    {
        _cts?.Cancel();
        _cts?.Dispose();
        _cts = null;
    }

    private async UniTask RunLoopAsync(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            await UniTask.Delay(1000, cancellationToken: ct);
            DoPeriodicWork();
        }
    }
}
```

### Pattern 3: Linked Tokens (Combine Destroy + Manual Cancel)

```csharp
public class LinkedTokenExample : MonoBehaviour
{
    private CancellationTokenSource _actionCts;

    public async UniTask PerformActionAsync()
    {
        // Cancel previous action if still running
        _actionCts?.Cancel();
        _actionCts?.Dispose();
        _actionCts = new CancellationTokenSource();

        // Link with destroy token so it cancels on either condition
        CancellationToken destroyCt = this.GetCancellationTokenOnDestroy();
        CancellationTokenSource linked = CancellationTokenSource.CreateLinkedTokenSource(
            _actionCts.Token, destroyCt);

        try
        {
            await DoWorkAsync(linked.Token);
        }
        catch (OperationCanceledException)
        {
            // Expected on cancellation — do nothing
        }
        finally
        {
            linked.Dispose();
        }
    }
}
```

### Handling OperationCanceledException

```csharp
public async UniTask LoadDataAsync(CancellationToken ct)
{
    try
    {
        await SomeAsyncOperation(ct);
    }
    catch (OperationCanceledException)
    {
        // Normal cancellation — cleanup silently
        return;
    }
    catch (Exception ex)
    {
        // Actual error — log and handle
        Debug.LogException(ex);
    }
}
```

## PlayerLoop Integration

UniTask hooks into Unity's PlayerLoop for precise timing control.

```csharp
// Available timing points
await UniTask.Yield(PlayerLoopTiming.Initialization);
await UniTask.Yield(PlayerLoopTiming.EarlyUpdate);
await UniTask.Yield(PlayerLoopTiming.FixedUpdate);
await UniTask.Yield(PlayerLoopTiming.PreUpdate);
await UniTask.Yield(PlayerLoopTiming.Update);
await UniTask.Yield(PlayerLoopTiming.PreLateUpdate);
await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate);

// Wait for specific timing in FixedUpdate
await UniTask.WaitForFixedUpdate(ct);

// Wait for end of frame (replacement for WaitForEndOfFrame coroutine)
await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate, ct);
```

## WhenAll / WhenAny — Parallel Execution

```csharp
// Wait for all tasks to complete (parallel)
(int score, string name) = await UniTask.WhenAll(
    LoadScoreAsync(ct),
    LoadNameAsync(ct)
);

// Wait for first task to complete
int winnerIndex = await UniTask.WhenAny(
    WaitForInputAsync(ct),
    WaitForTimeoutAsync(5f, ct)
);

// Typed WhenAny with result
(bool hasResult, int result) = await UniTask.WhenAny(
    FetchFromCacheAsync(ct),
    FetchFromNetworkAsync(ct)
);

// Load multiple assets in parallel
var textures = await UniTask.WhenAll(
    paths.Select(p => LoadTextureAsync(p, ct))
);
```

## Forget and Fire-and-Forget

```csharp
// Fire and forget — logs exceptions to Debug.LogException
DoSomethingAsync(ct).Forget();

// Suppress specific cancellation exceptions
DoSomethingAsync(ct).SuppressCancellationThrow().Forget();
```

## UniTaskCompletionSource — Manual Completion

For wrapping callback-based APIs or creating custom awaitable operations.

```csharp
public class DialogSystem : MonoBehaviour
{
    private UniTaskCompletionSource<DialogResult> _dialogTcs;

    public async UniTask<DialogResult> ShowDialogAsync(string message, CancellationToken ct)
    {
        _dialogTcs = new UniTaskCompletionSource<DialogResult>();

        // Register cancellation
        ct.Register(() => _dialogTcs.TrySetCanceled());

        ShowDialogUI(message);
        return await _dialogTcs.Task;
    }

    // Called by UI buttons
    public void OnConfirmClicked() => _dialogTcs.TrySetResult(DialogResult.Confirm);
    public void OnCancelClicked() => _dialogTcs.TrySetResult(DialogResult.Cancel);
}
```

## Async LINQ

UniTask provides async LINQ operators for event streams.

```csharp
using Cysharp.Threading.Tasks.Linq;

// Async event stream from button clicks
button.OnClickAsAsyncEnumerable()
    .ForEachAsync(_ =>
    {
        Debug.Log("Clicked");
    }, ct);

// Throttled input
button.OnClickAsAsyncEnumerable()
    .ThrottleFirst(TimeSpan.FromSeconds(1))
    .ForEachAsync(_ => ProcessClick(), ct);

// Channel-based producer/consumer
var channel = Channel.CreateSingleConsumerUnbounded<int>();
channel.Writer.TryWrite(42);
await channel.Reader.ReadAllAsync(ct).ForEachAsync(item => Process(item));
```

## Integration with DOTween

Await DOTween animations using the DOTween-UniTask bridge.

```csharp
// Await a single tween
await transform.DOMove(targetPos, 1f)
    .SetEase(Ease.OutQuad)
    .ToUniTask(cancellationToken: ct);

// Await a sequence
Sequence seq = DOTween.Sequence();
seq.Append(transform.DOScale(1.2f, 0.2f));
seq.Append(transform.DOScale(1f, 0.2f));
await seq.ToUniTask(cancellationToken: ct);

// Sequential animation chain
await transform.DOMove(pointA, 0.5f).ToUniTask(cancellationToken: ct);
await transform.DOMove(pointB, 0.5f).ToUniTask(cancellationToken: ct);
await transform.DOMove(pointC, 0.5f).ToUniTask(cancellationToken: ct);
```

## Integration with Addressables

```csharp
// Load asset
GameObject prefab = await Addressables.LoadAssetAsync<GameObject>("EnemyPrefab")
    .ToUniTask(cancellationToken: ct);

// Instantiate
GameObject instance = await Addressables.InstantiateAsync("EnemyPrefab", position, rotation)
    .ToUniTask(cancellationToken: ct);

// Load scene
await Addressables.LoadSceneAsync("GameScene", LoadSceneMode.Additive)
    .ToUniTask(cancellationToken: ct);
```

## Common Patterns

### Async Initialization Chain

```csharp
public class GameBootstrap : MonoBehaviour
{
    private async UniTaskVoid Start()
    {
        CancellationToken ct = this.GetCancellationTokenOnDestroy();

        try
        {
            await InitializeServicesAsync(ct);
            await LoadPlayerDataAsync(ct);
            await PreloadAssetsAsync(ct);
            await LoadGameSceneAsync(ct);
        }
        catch (OperationCanceledException)
        {
            Debug.Log("Bootstrap cancelled");
        }
        catch (Exception ex)
        {
            Debug.LogException(ex);
        }
    }
}
```

### Async State Machine

```csharp
public class EnemyAI : MonoBehaviour
{
    private CancellationTokenSource _cts;

    private void OnEnable()
    {
        _cts = new CancellationTokenSource();
        RunAIAsync(_cts.Token).Forget();
    }

    private void OnDisable()
    {
        _cts?.Cancel();
        _cts?.Dispose();
    }

    private async UniTask RunAIAsync(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            await PatrolAsync(ct);
            await ChaseAsync(ct);
            await AttackAsync(ct);
            await UniTask.Yield(ct);
        }
    }

    private async UniTask PatrolAsync(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested && !CanSeePlayer())
        {
            MoveToNextWaypoint();
            await UniTask.Delay(100, cancellationToken: ct);
        }
    }
}
```

### Timeout Wrapper

```csharp
public static async UniTask<T> WithTimeout<T>(
    UniTask<T> task,
    float timeoutSeconds,
    CancellationToken ct)
{
    int winnerIndex = await UniTask.WhenAny(
        task,
        UniTask.Delay(TimeSpan.FromSeconds(timeoutSeconds), cancellationToken: ct)
            .ContinueWith(() => default(T))
    );

    if (winnerIndex == 1)
        throw new TimeoutException($"Operation timed out after {timeoutSeconds}s");

    return await task;
}
```

### Debounced Input

```csharp
private async UniTask ProcessSearchInputAsync(TMP_InputField input, CancellationToken ct)
{
    string previousText = string.Empty;

    while (!ct.IsCancellationRequested)
    {
        await UniTask.WaitUntilValueChanged(input, i => i.text, cancellationToken: ct);

        // Debounce: wait 300ms after last change
        await UniTask.Delay(300, cancellationToken: ct);

        string currentText = input.text;
        if (currentText != previousText)
        {
            previousText = currentText;
            await PerformSearchAsync(currentText, ct);
        }
    }
}
```

## Anti-Patterns

### Do not use async void anywhere

```csharp
// BAD — exception vanishes, no cancellation
public async void OnButtonClicked() { ... }

// GOOD
public async UniTaskVoid OnButtonClickedAsync()
{
    CancellationToken ct = this.GetCancellationTokenOnDestroy();
    await HandleClickAsync(ct);
}
```

### Do not forget cancellation tokens

```csharp
// BAD — runs forever even after object is destroyed
public async UniTask BadMethod()
{
    await UniTask.Delay(5000);
    transform.position = Vector3.zero; // MissingReferenceException if destroyed
}

// GOOD
public async UniTask GoodMethod(CancellationToken ct)
{
    await UniTask.Delay(5000, cancellationToken: ct);
    transform.position = Vector3.zero;
}
```

### Do not use Task.Run or Task.Delay in Unity

```csharp
// BAD — runs on thread pool, not main thread
await Task.Run(() => transform.position = Vector3.zero);

// BAD — System.Threading timer, not Unity time-aware
await Task.Delay(1000);

// GOOD
await UniTask.SwitchToMainThread();
await UniTask.Delay(1000);
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.