async & await
Keywordsasync, await, Task, Awaitable, CancellationToken
NamespacesSystem.Threading.Tasks, UnityEngine (Awaitable)
In UnityUnity 2023.1+ / Unity 6: Awaitable; any version: Task

async & await

async/await is C#'s built-in way to write code that waits without freezing the game. It's an alternative to coroutines. It reads top to bottom like normal code, can return values, and works with try/catch.

Ordering at a coffee shop. Without await, you'd stand frozen at the counter until your coffee is ready, and nobody else can order. With await, you get a buzzer, go sit down (the game keeps running), and when it buzzes you come back and continue exactly where you left off.

Awaitable (Unity 6)

Unity 6 includes Awaitable, which is designed for games: it's allocation-friendly and continues on Unity's main thread, so you can safely touch GameObjects after awaiting.

MethodLike the coroutine...
await Awaitable.NextFrameAsync();yield return null;
await Awaitable.WaitForSecondsAsync(1f);yield return new WaitForSeconds(1f);
await Awaitable.FixedUpdateAsync();yield return new WaitForFixedUpdate();
await Awaitable.EndOfFrameAsync();yield return new WaitForEndOfFrame();
await Awaitable.BackgroundThreadAsync();(no equivalent) move to a background thread for heavy work
await Awaitable.MainThreadAsync();(no equivalent) come back to the main thread
await SceneManager.LoadSceneAsync("Level1");yield return SceneManager.LoadSceneAsync(...)

Code Samples

A countdown

using UnityEngine;

public class Countdown : MonoBehaviour
{
    async void Start() // "async void" is OK for Unity event methods like Start
    {
        for (int i = 3; i > 0; i--)
        {
            Debug.Log(i);
            await Awaitable.WaitForSecondsAsync(1f, destroyCancellationToken);
        }
        Debug.Log("Go!");
    }
}

Returning a value

Something coroutines can't do directly:

using UnityEngine;

public class DoorPuzzle : MonoBehaviour
{
    [SerializeField] private Door door;

    async void Start()
    {
        bool solved = await WaitForPlayerAnswer(10f);
        if (solved) door.Open();
    }

    async Awaitable<bool> WaitForPlayerAnswer(float timeout)
    {
        float end = Time.time + timeout;
        while (Time.time < end)
        {
            if (Input.GetKeyDown(KeyCode.Y)) return true;
            await Awaitable.NextFrameAsync(destroyCancellationToken);
        }
        return false;
    }
}

Heavy work off the main thread

async Awaitable GenerateMapAsync()
{
    await Awaitable.BackgroundThreadAsync();   // now on a worker thread
    int[,] tiles = BuildNoiseMap(512, 512);    // heavy pure-C# math: OK here
    // transform.position = ...;               // NOT OK: Unity APIs need the main thread

    await Awaitable.MainThreadAsync();         // back on the main thread
    SpawnTiles(tiles);                         // now Unity APIs are fine
}

Cancellation: stopping when the object is destroyed

Unlike coroutines, async methods don't stop automatically when the GameObject is destroyed. They keep running and then crash on the destroyed object. Pass a CancellationToken: every MonoBehaviour has destroyCancellationToken (Unity 2022.2+), which is cancelled when it's destroyed.

async void Start()
{
    try
    {
        while (true)
        {
            transform.Rotate(0f, 90f, 0f);
            await Awaitable.WaitForSecondsAsync(1f, destroyCancellationToken);
        }
    }
    catch (System.OperationCanceledException)
    {
        // Object was destroyed: normal, nothing to do.
    }
}

What happens at runtime

  1. Calling an async method runs it normally until the first await on something that isn't finished yet.
  2. At that await, the method returns control to its caller, and the rest of the method is saved as a "continuation" (like a coroutine's bookmark).
  3. When the awaited thing completes, the continuation runs. With Awaitable on the main thread, this happens during Unity's player loop, just like a coroutine.
  4. An exception inside an async Awaitable method is stored and re-thrown where it's awaited. In async void, it's logged to the Console.

Coroutine or async?

Coroutineasync / Awaitable
Unity versionsAllAwaitable: 2023.1+ / Unity 6
Stops when object destroyedAutomaticallyOnly with a cancellation token
Return a valueNo (use a callback)Yes
try/catch around waitsNoYes
Background threadsNoYes
Found in tutorialsVery commonGrowing

Common Mistakes

See also