Async/Await: What Really Happens
Async does not make code faster. It stops a thread from standing around waiting.
Guess first
Pick an answer. You will see if you were right at the end of the lesson.
Why is async/await useful on a web server?
Real-world analogy
What is it?
Async/await lets a method start slow work, such as a database query. It releases its thread while waiting and continues when the result arrives. The compiler rewrites an async method into a state machine that remembers where it stopped. This lets a server handle many requests with a small number of threads.
Real-world relevance
A dashboard endpoint needs orders from one service and users from another. Awaiting them one after the other takes the sum of both times. Starting both and using Task.WhenAll takes only the slower one. In the same app, one controller that calls .Result can freeze the whole site when traffic grows.
Key points
- Async frees threads — Async is for waiting on I/O, like a database or an HTTP call. While waiting, the thread goes back to the pool and serves other requests. The call itself is not faster.
- Task and Task of T — A Task is a promise of work that will finish. Task<T> is a promise that also gives a value of type T when done.
- What await does — await checks whether the work is done. If not, it returns the thread to the pool. The rest of the method runs when the work completes.
- Async all the way — If a method awaits, its caller should await it, up to the entry point. Mixing sync and async breaks the benefit and can cause deadlocks.
- Never .Result or .Wait() — They block a thread while the task finishes. Under load this starves the thread pool, and with an old synchronization context it can deadlock.
- async void is for events — An async void method cannot be awaited and its exceptions can crash the process. Use it only for event handlers, and use async Task everywhere else.
- CancellationToken and WhenAll — Pass a CancellationToken down so work stops when the client leaves. Use Task.WhenAll to run independent calls at the same time.
- ConfigureAwait in libraries — ConfigureAwait(false) matters in shared libraries that may run under a sync context. ASP.NET Core has none, so you do not need it in app code.
Quick check
Answer from memory — scrolling up first makes it stick less.
What is the main problem with calling .Result on an async method in a controller?
Two independent HTTP calls take 2 seconds each. Awaited one after the other, then with Task.WhenAll. About how long does each take?
Code example
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient();
var app = builder.Build();
app.MapGet("/dashboard", async (IHttpClientFactory factory, CancellationToken ct) =>
{
var client = factory.CreateClient();
// Start both calls first, so they run at the same time
var ordersTask = client.GetStringAsync("https://api.example.com/orders", ct);
var usersTask = client.GetStringAsync("https://api.example.com/users", ct);
// Wait for both without blocking a thread
await Task.WhenAll(ordersTask, usersTask);
return Results.Ok(new
{
Orders = await ordersTask,
Users = await usersTask
});
});
app.Run();Line-by-line walkthrough
- 1. AddHttpClient registers IHttpClientFactory so the endpoint can ask for it.
- 2. The handler is async and takes a CancellationToken. ASP.NET Core fills it in, and it fires if the client disconnects.
- 3. The two GetStringAsync calls each start right away and return a Task. Both get the token, so a closed browser tab cancels them.
- 4. await Task.WhenAll waits for both, and the thread goes back to the pool while it waits.
- 5. Awaiting each task again gives its string. They are already finished, so this does not wait.
- 6. Results.Ok sends both strings back as one JSON object with Orders and Users fields.
Spot the bug
[HttpGet("{id}")]
public IActionResult Get(int id)
{
var order = _service.GetOrderAsync(id).Result;
if (order is null) return NotFound();
return Ok(order);
}Need a hint?
Show answer
Explain like I'm 5
Fun fact
Hands-on challenge
More resources
- Asynchronous programming with async and await (Microsoft Learn)
- Task asynchronous programming model (Microsoft Learn)
Lock it in