01 / THE DECISION AT AWAIT CHECK FIRST / SUSPEND ONLY IF NEEDED
The caller supplies a task representing a read. The method returns a different task representing the count.
THE C# METHOD
static async Task<int> CountAsync(
Task<string> readTask)
{
string text = await readTask;
return text.Length;
}
readTaskTask<string> · the read result
CountAsync(...)Task<int> · the method result
Get the result immediately.
No suspension at this await.
Save the resume point and awaiter.
Continue after completion.
IMPORTANT DISTINCTION The read can finish before the method's returned task finishes.
03 / WHAT THE COMPILER KEEPS A RESUME POINT / NOT A RESERVED THREAD
WHILE THE METHOD IS SUSPENDED
A conceptual view. Generated fields and optimizations vary.
The body starts synchronously.
Naturally asynchronous I/O need not occupy a waiting thread.
An already-completed awaiter takes the fast path. A continuation need not change threads.
The read task delivers text.
The count task delivers a number, or a fault.
THE MENTAL MODEL Split execution at an incomplete await. Preserve state. Continue when ready.
Sources: C# await operator · Task-based async model · C# async functions / state machines
This deck models a Task-returning C# method awaiting a naturally asynchronous I/O operation. It is a browser-local teaching simulation, not a running .NET process, profiler trace, benchmark, or exact compiler dump. Calling an async method begins running its body synchronously. When an awaiter is incomplete, the method can suspend and return control to its caller. The thread is not blocked by that await. The caller may continue or itself await. A Task is an operation/completion representation, not a dedicated thread. The execution lane is a logical view of this method, not a fixed OS thread ID. Source: https://learn.microsoft.com/dotnet/csharp/language-reference/operators/await
The example is adapted to take a Task<string> so the demonstration can control whether the awaited operation is pending or already complete. A real caller might supply a task returned by HttpClient.GetStringAsync, but this deck performs no network I/O. Successful scenarios use the string "hello", whose Length is 5. The awaiter protocol checks IsCompleted. An incomplete await stores state and arranges a continuation; an already-complete await proceeds without suspending. GetResult retrieves a successful result or throws for a failed/canceled operation. This diagram focuses on Task<T>. Scheduling/context behavior, multiple awaits, cancellation, race handling, and exact generated field layout are omitted. Sources: https://learn.microsoft.com/dotnet/csharp/asynchronous-programming/task-asynchronous-programming-model https://learn.microsoft.com/dotnet/csharp/language-reference/operators/await
The left and right panels are driven by one state snapshot. Use Step to inspect the exact relationships, especially while I/O is pending and just after it finishes. The bright outline indicates the current state, also named in the header. Mint paths have already been visited. Thread identity/scheduler details are abstracted. The input readTask and the method's return task are distinct. While suspended, readTask is pending, the count task is pending and held by the caller, and this method does not occupy a thread merely to wait. After readTask completes, the continuation gets its value and finishes the count task. In the already-completed scenario, the method does not suspend: it executes its body and returns an already-completed count task to the caller. The slowed-down animation represents conceptual steps, not actual asynchronous scheduling here. In the fault scenario the read task completes faulted, GetResult throws inside the method, and the uncaught exception faults the returned task. Cancellation, context capture, inline continuation races, and multiple await points are omitted. Resume can run on the same or another thread, depending on context/scheduling. The diagram's labels are conceptual, not exact compiler-generated numeric states. Print/no-JS/presenter views always retain a neutral all-paths diagram and captions.
The compiler transforms a Task-returning async method into a state machine and associated builder. This is a high-level mental model, not an exact layout: optimization level and compiler/runtime versions affect generated code. The saved state does not preserve an entire blocked thread stack. Required locals can be lifted; values not needed across suspension need not be stored. GetResult may throw. The async machinery reports an uncaught exception through the returned task. A real caller should observe/await that task and handle failures. Do not infer fixed thread IDs, forced thread-pool hops, equal task identities, or guaranteed performance improvements from this illustrative diagram. The sample method and controlled completed/pending/faulted inputs were checked with .NET 10. Animations themselves are native browser SVG/CSS/JavaScript.