Lesson 94 +10 XP

Async/Await and the Awaitable Class

Async/Await and the Awaitable Class

Unity 6 introduced the Awaitable class as a first-class alternative to coroutines for asynchronous code. Both C# Task-based async/await and Unity's Awaitable are supported.

Standard C# async/await in Unity

You can use standard C# async/await patterns in Unity scripts. Tasks resume in the Update phase by default.

using System.Threading.Tasks;
using UnityEngine;

public class AsyncExample : MonoBehaviour
{
    async void Start()
    {
        Debug.Log("Starting async load...");
        await LoadDataAsync();
        Debug.Log("Data loaded!");
    }

    async Task LoadDataAsync()
    {
        // Simulates an async operation (e.g., web request)
        await Task.Delay(2000); // waits 2 real seconds
    }
}

Limitations of Task in Unity:

  • Do not run on the main thread by default if using Task.Run().
  • Cannot access Unity API from background threads.
  • No built-in integration with Unity's lifecycle (destroy, scene change).

Unity Awaitable Class

Unity's Awaitable is a lightweight, garbage-free alternative designed specifically for Unity:

using UnityEngine;

public class AwaitableExample : MonoBehaviour
{
    async Awaitable Start()
    {
        // Wait for next frame (like yield return null in a coroutine)
        await Awaitable.NextFrameAsync();
        Debug.Log("Next frame reached");

        // Wait until end of frame (after rendering)
        await Awaitable.EndOfFrameAsync();

        // Wait for a fixed update step
        await Awaitable.FixedUpdateAsync();

        // Wait for real seconds (unaffected by timeScale)
        await Awaitable.WaitForSecondsAsync(1.5f);
    }
}

Async Scene Loading

using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneLoader : MonoBehaviour
{
    async Awaitable LoadSceneAsync(string sceneName)
    {
        AsyncOperation op = SceneManager.LoadSceneAsync(sceneName);
        op.allowSceneActivation = false;

        // Wait until load is at 90% (Unity holds at 0.9 until activation)
        while (op.progress < 0.9f)
        {
            await Awaitable.NextFrameAsync();
        }

        // Activate the scene
        op.allowSceneActivation = true;
    }
}

Cancellation with CancellationToken

Use a CancellationToken to cancel async operations when a script is destroyed:

using System.Threading;
using UnityEngine;

public class CancelExample : MonoBehaviour
{
    private CancellationTokenSource _cts;

    void Start()
    {
        _cts = new CancellationTokenSource();
        RunAsync(_cts.Token);
    }

    async void RunAsync(CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            await Awaitable.NextFrameAsync(token);
            // Do work each frame
        }
    }

    void OnDestroy()
    {
        _cts.Cancel();
        _cts.Dispose();
    }
}

TL;DR

  • Standard C# async/await (Task-based) works in Unity but has threading caveats.
  • Awaitable is Unity's lightweight, garbage-free async primitive.
  • Use Awaitable.NextFrameAsync(), FixedUpdateAsync(), WaitForSecondsAsync() as coroutine-equivalents.
  • Always cancel async work in OnDestroy using a CancellationToken.