| Keywords | async, await, Task, Awaitable, CancellationToken |
|---|---|
| Namespaces | System.Threading.Tasks, UnityEngine (Awaitable) |
| In Unity | Unity 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.
| Method | Like 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
- Calling an
asyncmethod runs it normally until the firstawaiton something that isn't finished yet. - 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). - When the awaited thing completes, the continuation runs. With
Awaitableon the main thread, this happens during Unity's player loop, just like a coroutine. - An exception inside an
async Awaitablemethod is stored and re-thrown where it's awaited. Inasync void, it's logged to the Console.
Coroutine or async?
| Coroutine | async / Awaitable | |
|---|---|---|
| Unity versions | All | Awaitable: 2023.1+ / Unity 6 |
| Stops when object destroyed | Automatically | Only with a cancellation token |
| Return a value | No (use a callback) | Yes |
| try/catch around waits | No | Yes |
| Background threads | No | Yes |
| Found in tutorials | Very common | Growing |
Common Mistakes
- No cancellation. The method outlives its object and throws
MissingReferenceException. PassdestroyCancellationToken. - Using
Task.Delayfor game timing.Task.Delayruns on the system clock: it ignoresTime.timeScaleand knows nothing about Unity's frames, and in the Editor a pendingTaskcan keep running after you exit Play Mode. PreferAwaitablemethods, which run inside Unity's player loop. - Using Unity APIs on a background thread. Most Unity APIs throw ("can only be called from the main thread"). Switch back first.
async voidfor your own methods. Callers can't await it or catch its exceptions. Useasync Awaitable(orTask); keepasync voidfor Unity messages likeStartand UI callbacks.- Awaiting the same Awaitable twice. Awaitables are pooled and reused; await each one only once.
See also
This page is a simplified, AI-assisted interpretation. For the complete and authoritative reference, see the official Microsoft C# documentation.