Game Dev Cheat Sheet

Coroutine vs Async/Awaitable

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()
{
    await Awaitable.WaitUntilAsync(() => isDone);
    Debug.Log("Condition met");
}

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()
{
    await Awaitable.WhenAll(
        DoAAsync(),
        DoBAsync()
    );
    Debug.Log("Both complete");
}

Cancellation

Coroutine

Coroutine routine;

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

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

Async/Awaitable

CancellationTokenSource cts;

async void Start()
{
    cts = new CancellationTokenSource();
    try
    {
        await MyAsync(cts.Token);
    }
    catch (OperationCanceledException)
    {
        Debug.Log("Cancelled cleanly");
    }
}

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 trackingWhenAll / WhenAny
Requires MonoBehaviourYesNo (Unity 6)
Unit testableDifficultStandard async testing
Available sinceUnity 4+Unity 6 / UniTask for older