# 2. Awaitable Waits and Thread Switches
Part of [[Async Code]]. Previous: [[1. Awaitable versus Coroutines and Task]]. Next: [[3. Cancellation Tokens]].
> [!note] Verification status
> Static methods and the main-thread restriction on `WaitForSecondsAsync` are from the 6000.5 `Awaitable` Scripting API. Thread-switch behaviour is from the continuation page. **Code not compiled in Unity.**
## 2.1 The built-in waits
| Method | Resumes |
|---|---|
| `Awaitable.NextFrameAsync(cancellationToken = default)` | On the next frame |
| `Awaitable.EndOfFrameAsync(...)` | After all Unity subsystems have run for the current frame |
| `Awaitable.FixedUpdateAsync(...)` | On the next fixed update |
| `Awaitable.WaitForSecondsAsync(seconds, cancellationToken = default)` | After `seconds` (game time). **Main-thread only; always completes on the main thread.** |
| `Awaitable.FromAsyncOperation(asyncOperation, ...)` | When a Unity `AsyncOperation` completes |
All of the ones that take a `CancellationToken` throw `OperationCanceledException` if the token is canceled (part 3). That **aborts** the wait — you will not fall through to the next line unless you catch the exception. The `WaitForSecondsAsync` page is explicit about this: if you used cancellation as "the task finished early, continue," you must catch or you will skip the follow-up work.
```csharp
using System.Threading;
using UnityEngine;
public sealed class DoorReveal : MonoBehaviour
{
public async Awaitable RevealAfterAsync(float delay, CancellationToken token)
{
await Awaitable.WaitForSecondsAsync(delay, token);
// If token canceled during the wait, we never reach here.
gameObject.SetActive(true);
}
}
```
`NextFrameAsync` is the replacement for `yield return null`. `FixedUpdateAsync` is the replacement for `WaitForFixedUpdate` when you are stepping physics ([[Physics 3D]]). Prefer `NextFrameAsync` plus a condition over a long `WaitForSecondsAsync` when you are waiting *for something to happen* (a panel to appear, a body to sleep).
## 2.2 Wrap an `AsyncOperation`
```csharp
using UnityEngine;
using UnityEngine.SceneManagement;
public static class SceneAwait
{
public static async Awaitable LoadSingleAsync(string sceneName, System.Threading.CancellationToken token)
{
AsyncOperation op = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
if (op == null) return;
// If your 6000.5 Scripting Reference shows a CancellationToken overload, pass token here.
await Awaitable.FromAsyncOperation(op);
}
}
```
If a Unity API already returns something awaitable, use that. `FromAsyncOperation` is the adapter for APIs that still hand you a raw `AsyncOperation`. Addressables 2.9 handles are *not* `AsyncOperation`; use `handle.Task`, a coroutine `yield return handle`, or 4.x `ToAwaitable()` ([[3. Loading Instantiating and Releasing]]).
## 2.3 Switching threads
```csharp
using UnityEngine;
public sealed class HeightmapBakeNote : MonoBehaviour
{
public async Awaitable<float> ComputeAverageHeightAsync(float[] heights)
{
await Awaitable.BackgroundThreadAsync();
// We are on a thread-pool thread. Do not touch Unity objects, transforms, or GetComponent.
double sum = 0;
for (int i = 0; i < heights.Length; i++)
sum += heights[i];
float average = heights.Length == 0 ? 0f : (float)(sum / heights.Length);
// No need to hop back here if the *caller* awaited us from the main thread —
// the official continuation rule resumes the caller on the main thread.
return average;
}
public async Awaitable Start()
{
float[] sample = new float[1024];
float average = await ComputeAverageHeightAsync(sample);
Debug.Log("Average height " + average); // main thread, safe
}
}
```
If you are on a background thread and need to call a Unity API *inside the same method*, `await Awaitable.MainThreadAsync()` first. That hop **waits until the next main-thread Update** if you are coming from the background — it is the expensive direction. Do as much as you can on the background, return a result, and let the caller (already on main) apply it.
Rules that will save a lab:
- No `GameObject`, `Transform`, `Renderer`, or `ScriptableObject` access off the main thread
- Copy the data you need into arrays *before* you hop
- Do not use `WaitForSecondsAsync` off the main thread (the API forbids it)
For large grid work on terrain, prefer Jobs/Burst ([[2. Jobs and Burst for GameObject Projects]]) over a thread-pool loop.
## Official docs
- Scripting API: https://docs.unity3d.com/6000.5/Documentation/ScriptReference/Awaitable.html
- WaitForSecondsAsync: https://docs.unity3d.com/6000.5/Documentation/ScriptReference/Awaitable.WaitForSecondsAsync.html
- Continuations: https://docs.unity.com/en-us/engine/6000.5/manual/scripting/optimization/async-await-support/async-awaitable-continuations