.NET / ASYNC UNDER THE SURFACE

Await the work.
Not the thread.

Follow the execution.
Watch the state machine keep its place.

C# async / awaitTasksContinuations

ONE METHOD / TWO VIEWSDEMO 03
An async method can suspend while I/O continues Execution runs, suspends at an incomplete await, and resumes after completion. A saved state connects the two execution segments without a dedicated thread waiting for the I/O. METHOD EXECUTION RUN SUSPEND RESUME SAVED STATE awaiter + resume point not a waiting thread I/O PROCEEDS INDEPENDENTLY

One logical method. More than one execution segment.

AN ILLUSTRATIVE .NET MODEL Arrow keys to explore · Synchronized lab on slide 3

01 / THE DECISION AT AWAIT CHECK FIRST / SUSPEND ONLY IF NEEDED

The same code. Two possible paths.

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

AWAITER ALREADY COMPLETED

Keep going.

Get the result immediately.
No suspension at this await.

RunGetResultFinish
AWAITER NOT YET COMPLETED

Save. Yield. Resume.

Save the resume point and awaiter.
Continue after completion.

RunSuspendResume

IMPORTANT DISTINCTION The read can finish before the method's returned task finishes.

02 / WATCH BOTH SIDES SYNCHRONIZED / NOT REAL TIMINGS

Execution on the left. State on the right.

COUNTASYNC / INPUT "hello"STATIC MAP / ALL PATHSREADY

EXECUTION + TASK LIFETIMES

01 static async Task<int> CountAsync(Task<string> readTask)02 {03     string text = await readTask;04     return text.Length;05 }
THIS METHOD / CPURuns → yields → resumes
READ TASK / STRINGPending → value or error
COUNT TASK / INTPending → 5 or fault

SAVED STATEResume point + awaiter when suspended

CONCEPTUAL COMPILER STATE MACHINE

Async await state transitions synchronized with execution Run synchronously, then inspect whether the awaiter is completed. If not, save state and suspend, then resume on completion. If already complete, go directly to GetResult. A successful result completes the method's task with 5; an uncaught error faults it. The two tasks and the method's execution are shown separately on the left. Run synchronously Awaitercompleted? Save + suspendReturn pending task ResumeRestore saved state GetResult()Value or exception Complete with 5 Fault the task NOYESon completioncontinuevaluethrows

Read the branches: incomplete → save / resume; already complete → direct to GetResult. No dedicated thread waits for the pictured I/O.

03 / WHAT THE COMPILER KEEPS A RESUME POINT / NOT A RESERVED THREAD

The method remembers where to continue.

WHILE THE METHOD IS SUSPENDED

RESUME POINTWhich await should continue?
AWAITERHow to observe completion and get the result.
NEEDED VALUESOnly state that must survive suspension.
METHOD BUILDERCompletes or faults the returned task.

A conceptual view. Generated fields and optimizations vary.

01 / NO MAGIC THREAD

async does not mean Task.Run.

The body starts synchronously.
Naturally asynchronous I/O need not occupy a waiting thread.

02 / NO GUARANTEED YIELD

await can keep going.

An already-completed awaiter takes the fast path. A continuation need not change threads.

03 / NO CONFUSION ABOUT TASKS

Completion belongs to an operation.

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.