How the await Keyword Works
If async prepares a method, await is where the real work happens. In simple words, await means:
"If this task is not finished yet, pause this method here, give the thread back so it can do other work, and continue from the next line when the task is done."
What happens at an await, step by step
When the program reaches await someTask:
- Check: is
someTaskalready finished?- Yes: take the result and continue immediately, like normal code. No pause at all.
- No: go to step 2.
- Save: remember where the method stopped and the values of its local variables.
- Register: tell the task "when you finish, run the rest of this method".
- Return: the method returns an unfinished
Taskto its caller. The thread is now free. - Resume: when the task finishes, the rest of the method runs from the line after
await. - Unwrap:
awaitgives you the result of the task (forTask<T>), or throws the task's exception.
await unwraps the result
Task<string> task = File.ReadAllTextAsync("notes.txt"); // a Task<string>
string text = await task; // a string
// Usually written in one line
string text2 = await File.ReadAllTextAsync("notes.txt");
Without await you only have the "receipt" (Task<string>). With await you get the actual value (string).
await does not block the thread
The word "wait" is in await, but it does not block. Compare:
| Code | Waits for result? | Blocks the thread? |
|---|---|---|
await task | Yes | No: the thread is free while waiting |
task.Result | Yes | Yes: the thread is stuck until it finishes |
task.Wait() | Yes | Yes |
The method pauses, but the thread does not. That is the most important idea in this whole series.
Sequential awaits vs concurrent awaits
Where you put await decides whether tasks run one after another or at the same time.
static async Task<string> GetDataAsync(string name, int ms)
{
await Task.Delay(ms);
return $"{name} loaded";
}
// 1. Sequential: the second call starts only after the first finishes.
// Total: 2 + 3 = about 5 seconds
string customers = await GetDataAsync("Customers", 2000);
string orders = await GetDataAsync("Orders", 3000);
// 2. Concurrent: start both first, await later.
// Total: about 3 seconds (the longest one)
Task<string> customersTask = GetDataAsync("Customers", 2000);
Task<string> ordersTask = GetDataAsync("Orders", 3000);
string customers2 = await customersTask;
string orders2 = await ordersTask;
// 3. Concurrent with Task.WhenAll (cleanest)
string[] all = await Task.WhenAll(
GetDataAsync("Customers", 2000),
GetDataAsync("Orders", 3000));
Use sequential awaits when the second call needs the result of the first (for example, get the user, then get that user's orders). Use concurrent awaits when the calls are independent.
await and exceptions
If the awaited task failed, await throws the original exception, so a normal try/catch works:
try
{
string page = await httpClient.GetStringAsync("https://example.com/missing-page");
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Request failed: {ex.Message}");
}
With .Result or .Wait(), the exception comes wrapped in an AggregateException, which is less convenient. Another reason to prefer await.
When you await Task.WhenAll and several tasks fail, await throws only the first exception. The others are still available in the task's Exception.InnerExceptions property.
Where the code continues after await
- WinForms / WPF / MAUI: after
await, the code continues on the UI thread, so you can safely update labels and text boxes. - ASP.NET Core and console apps: the code continues on any free thread-pool thread.
- Library code: you can write
await task.ConfigureAwait(false)to say "I don't need to come back to the original context". This avoids some deadlocks and is slightly faster.
Rules for await
awaitcan only be used in anasyncmethod (or in top-level statements inProgram.cs).- You can await
Task,Task<T>,ValueTask,ValueTask<T>, and any type that has aGetAwaiter()method. awaitworks insidetry,catchandfinallyblocks (C# 6+).awaitis not allowed inside alockblock. UseSemaphoreSlimwithawait semaphore.WaitAsync()instead.- Use
await foreachto loop over anIAsyncEnumerable<T>, andawait usingfor objects that are disposed asynchronously.
Summary
awaitpauses the method, not the thread.- If the task is already complete,
awaitjust continues without pausing. - It unwraps the result of
Task<T>and re-throws the original exception. - Start independent tasks first and await them later (or use
Task.WhenAll) to run them concurrently. - In UI apps the code after
awaitreturns to the UI thread; in ASP.NET Core it runs on any pool thread.