# 1. Awaitable versus Coroutines and Task Part of [[Async Code]]. Next: [[2. Awaitable Waits and Thread Switches]]. > [!note] Verification status > Comparisons below follow the Unity 6000.5 Awaitable manual and the completion/continuation page. The pooling rule ("not safe to await multiple times") is official. **Code not compiled in Unity.** ## 1.1 Three ways to not block the main thread | Model | What it is | Strength | Cost / catch | |---|---|---|---| | **Coroutine** (`IEnumerator` + `StartCoroutine`) | An iterator the PlayerLoop pumps each frame | Familiar, easy to `yield return WaitForSeconds`, cancelled when you `StopCoroutine` or destroy the behaviour (usually) | Awkward return values and try/catch; stack of nested enumerators; easy to leak if you start on a hidden object | | **`.NET Task`** | The standard C# async type | Interops with any .NET library; Test Framework can return `Task` from a `[Test]` | SynchronizationContext posts continuations to the *next* `Update`, which adds latency at scale; more allocation; easy to forget that Unity APIs are main-thread only | | **`Awaitable`** | Unity's pooled awaitable, used as `async Awaitable` / `async Awaitable<T>` | Same `await` syntax, cheaper than `Task`, resumes on the main thread if you started there (no "wait until next Update" tax by default) | **Pooled — do not await the same instance twice**; not a general .NET type; background work is not stopped automatically when you leave Play Mode | The 6000.5 manual's position is that `Awaitable` is **usually preferable to `Task`** for asynchronous *Unity* code. Keep `Task` when you are talking to a .NET API that returns one, or when a test method must return `Task` ([[3. UnityTest Async Tests and Domain Reloads]]). Keep coroutines when you are extending a large existing coroutine codebase and a rewrite would not pay for itself this week — or when a Unity API still only offers a `YieldInstruction`. Jobs + Burst ([[2. Jobs and Burst for GameObject Projects]]) remain the tool for *short, parallel, CPU-bound* work. `Awaitable.BackgroundThreadAsync` is for longer work you do not want on the main thread, not for 10,000 simple sums. ## 1.2 A first `async Awaitable` method ```csharp using UnityEngine; using UnityEngine.SceneManagement; public sealed class BootLoader : MonoBehaviour { [SerializeField] string _firstScene = "Harbor"; async Awaitable Start() { await SceneManager.LoadSceneAsync(_firstScene); await Awaitable.NextFrameAsync(); // First frame of the new scene has now run; safe to talk to objects that initialized in Start. } } ``` `LoadSceneAsync` returns an `AsyncOperation`. In Unity 6 you can `await` many `AsyncOperation`-style APIs directly, or wrap them with `Awaitable.FromAsyncOperation` (part 2) if your version or the API needs it. If `await sceneLoad` does not compile against a particular operation type, wrap it. `async Awaitable Start()` (or `Awake`, button handlers, etc.) is legal. Be careful with **async void** — prefer `async Awaitable` plus `LogExceptionsAndForget()` (part 5) if you must fire and forget, rather than `async void` which is easy to lose exceptions from. ## 1.3 Continuations (why Awaitable exists) When you `await` a `Task` that was started on the main thread, Unity's `UnitySynchronizationContext` typically resumes you on the **next Update**. That is correct (you are back on the main thread) and it is also a frame of latency, plus context-capture cost, if you do it a lot. `Awaitable` continuations, unless documented otherwise: - If the method was called from the **main thread**, it resumes on the main thread (without that "always next Update" tax as the reason for the type). - Otherwise it resumes on a **thread-pool** thread. Exceptions: `Awaitable.MainThreadAsync()` always resumes on the main thread; `Awaitable.BackgroundThreadAsync()` always resumes on a background thread. Those two are **local to the current method** — a callee that switches to the background does not permanently banish the *caller*. If the caller awaited from the main thread, the caller resumes on the main thread (official example in the continuation page). This is the whole trick. You can hop to a background thread to crunch numbers, `return 42`, and the caller on the main thread can then call `SceneManager.LoadSceneAsync` safely. ## 1.4 Pooling: the rule you will break once `Awaitable` instances are **pooled**. The official documentation is blunt: they are **not safe to `await` multiple times** in the same method (or at all). This is wrong: ```csharp Awaitable wait = Awaitable.NextFrameAsync(); await wait; await wait; // do not do this ``` Await the result of a call once, or call the helper again. Store a "logical operation" in your own field if you need to cancel it (`Cancel()`, part 3), but do not treat an `Awaitable` like a `Task` you can share with three subscribers. ## Official docs - Awaitable manual: https://docs.unity.com/en-us/engine/6000.5/manual/scripting/optimization/async-await-support - Continuations: https://docs.unity.com/en-us/engine/6000.5/manual/scripting/optimization/async-await-support/async-awaitable-continuations - Scripting API: https://docs.unity3d.com/6000.5/Documentation/ScriptReference/Awaitable.html