$ dotnet runStartFetchWorkingDatamain threadthread pool1000 msjob · Task<string>IsCompletedfalseResult…1Console.WriteLine("Start");2var job = GetAsync();3Console.WriteLine("Working");4var s = await job;5Console.WriteLine(s);1async Task<string> GetAsync()2{3 Console.WriteLine("Fetch");4 await Task.Delay(1000);5 return "Data";6}pausedpausedT1T2StartFetchWorkingDataTask"Data"
Front row: the code (Main and the async method GetAsync)Back row: the threads, the timer and the task jobTokens: threads T1 and T2 walking the lines, paused bookmarks, the task and its resultFront row: the top-level statements of Program.cs and the async method GetAsync. Back row: the console, Thread 1, the thread pool, the Task.Delay timer and the task job. Thread 1 runs until GetAsync awaits an unfinished task; GetAsync returns job early, Main prints Working, then pauses at await job, so none of your code holds a thread while it waits. When the timer fires, a pool thread finishes GetAsync, completes job and Main prints Data. Task.Delay(1000) completes after at least about 1000 ms; the exact moment depends on the system clock's resolution. Lines 1 to 4 take only microseconds, so the timer has barely started when Main pauses.

Running until await

  1. async and await let a method wait for slow work without holding a thread. A thread is a worker that runs code one line at a time. Thread 1 starts at line 1 and prints Start.

Pausing without blocking

Resuming

The rules

What happens

// step 1/10 · start
t = 0 ms   Thread 1   line 1   Console.WriteLine("Start");
console    Start

thread     a worker that runs code one line after another

Top-level statements that use await become an async Main that returns a Task. The hidden entry point that starts it simply waits for that Task, so the console program ends only after line 5. In an app with a window, the freed thread would go back to drawing it. Which thread runs a continuation depends on the SynchronizationContext. A console app has none, so continuations run on thread-pool threads; a desktop UI resumes on its UI thread. The figure shows one pool thread; in reality Main may resume on the same or another pool thread.