Game Dev Cheat Sheet

Unity Coroutine vs Async/Await

Side-by-side comparison of Unity coroutines and async/await with code for every common pattern.

Unity 6 introduced Awaitable, bringing proper async/await to the engine. But when should you use it over coroutines? This reference shows the same operations implemented both ways, so you can see the differences and decide which fits your project.

Wait for Seconds

Coroutine

IEnumerator Wait()
{
    yield return new WaitForSeconds(2f);
    Debug.Log("Done waiting");
}

Async/Awaitable

async Awaitable WaitAsync()
{
    await Awaitable.WaitForSecondsAsync(2f);
    Debug.Log("Done waiting");
}

Wait for Condition

Coroutine

IEnumerator WaitFor()
{
    yield return new WaitUntil(() => isDone);
    Debug.Log("Condition met");
}

Async/Awaitable

async Awaitable WaitForAsync()
{
    while (!isDone)
        await Awaitable.NextFrameAsync();
    Debug.Log("Condition met");
}
// Unity's built-in Awaitable does not include
// a WaitUntilAsync. Use a NextFrameAsync loop
// as shown, or use UniTask's
// UniTask.WaitUntil() if you have it installed.

Sequential Operations

Coroutine

IEnumerator RunSequence()
{
    yield return StartCoroutine(StepA());
    yield return StartCoroutine(StepB());
    yield return StartCoroutine(StepC());
    Debug.Log("All steps complete");
}

Async/Awaitable

async Awaitable RunSequenceAsync()
{
    await StepAAsync();
    await StepBAsync();
    await StepCAsync();
    Debug.Log("All steps complete");
}

Parallel Operations

Coroutine

IEnumerator RunParallel()
{
    bool aFinished = false;
    bool bFinished = false;

    StartCoroutine(DoA(() => aFinished = true));
    StartCoroutine(DoB(() => bFinished = true));

    yield return new WaitUntil(
        () => aFinished && bFinished
    );
    Debug.Log("Both complete");
}

Async/Awaitable

async Awaitable RunParallelAsync()
{
    // Awaitable does not support WhenAll natively.
    // Use Task.WhenAll by returning Task from each
    // method, or use UniTask.WhenAll if installed.
    Task a = DoAAsync();
    Task b = DoBAsync();
    await Task.WhenAll(a, b);
    Debug.Log("Both complete");
}
// If your async methods return Awaitable,
// convert to Task via .AsTask() (Unity 6) or
// switch to UniTask for full parallel support.

Cancellation

Coroutine

Coroutine routine;

void Start()
{
    routine = StartCoroutine(MyRoutine());
}

void Cancel()
{
    // No cleanup hook; just stops
    StopCoroutine(routine);
}

Async/Awaitable

CancellationTokenSource cts;

async Awaitable Start()
{
    cts = new CancellationTokenSource();
    try
    {
        await MyAsync(cts.Token);
    }
    catch (OperationCanceledException)
    {
        Debug.Log("Cancelled cleanly");
    }
}
// Avoid async void in Unity. Unhandled exceptions
// in async void methods are silently swallowed.
// Use async Awaitable for MonoBehaviour entry points.

void Cancel()
{
    cts.Cancel();
    cts.Dispose();
}

Return a Value

Coroutine

// Coroutines cannot return values directly.
// Use a callback or set a field instead.
IEnumerator LoadHP(System.Action<int> callback)
{
    yield return new WaitForSeconds(1f);
    callback(100);
}

// Usage
StartCoroutine(LoadHP(hp =>
{
    Debug.Log("HP: " + hp);
}));

Async/Awaitable

// Awaitable<T> returns a value directly.
async Awaitable<int> LoadHPAsync()
{
    await Awaitable.WaitForSecondsAsync(1f);
    return 100;
}

// Usage
int hp = await LoadHPAsync();
Debug.Log("HP: " + hp);

Summary Comparison

FeatureCoroutineAsync/Awaitable
Return valuesNo (use callbacks)Yes
Exception handlingNo try/catchFull try/catch/finally
CancellationStopCoroutine (no cleanup)CancellationToken (clean)
Parallel executionManual trackingTask.WhenAll (via .AsTask())
Requires MonoBehaviourYesNo (Unity 6)
Unit testableDifficultStandard async testing
Available sinceUnity 4+Unity 2023.1+ (Awaitable) / UniTask for earlier versions

Quick decision guide

  • Use coroutines when: simple time-based sequences, working with existing coroutine code, targeting older Unity versions.
  • Use async/await when: starting new code on Unity 2023.1+, need exception handling, want to use Task-based libraries, need cancellation tokens.
  • Mix freely. Both are fully supported and can call into each other.

Frequently asked questions

Should I use coroutines or async/await in Unity?
For new code in Unity 2023.1 and later, async/await with Awaitable is generally preferred: better debugging, exception handling, and integration with the C# ecosystem. Coroutines remain a fine choice for simple time-based sequences and are still well supported. Mix freely; avoid converting working code without reason.
Are async methods more expensive than coroutines?
Modern async/await in Unity has comparable or lower allocation cost than coroutines, especially with Awaitable. Older Task-based async could allocate more, but Awaitable was designed to fix this. Profile before assuming either is the bottleneck.
Can I await a coroutine?
Not directly. You can wrap a coroutine in a TaskCompletionSource or convert to Awaitable, but the simpler approach is to convert the coroutine to async. If you need both, expose two entry points that share a private async implementation.
What happens to a coroutine when the GameObject is disabled?
It stops automatically. Async methods do not. If you start an async method that touches a disabled or destroyed GameObject, you will hit MissingReferenceException. Use a CancellationToken from destroyCancellationToken to cancel cleanly.

Last updated: