Lesson 7 of 45 intermediate

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?

Open interactive version (quiz + challenge)

Real-world analogy

Think of a waiter who takes your order to the kitchen. A bad waiter stands at the kitchen door until the food is ready, and other tables get nothing. A good waiter goes to serve other tables and comes back when the bell rings. Here the waiter is a thread and the food is a database or network call. Await means leave the door and come back when the bell rings.

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

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. 1. AddHttpClient registers IHttpClientFactory so the endpoint can ask for it.
  2. 2. The handler is async and takes a CancellationToken. ASP.NET Core fills it in, and it fires if the client disconnects.
  3. 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. 4. await Task.WhenAll waits for both, and the thread goes back to the pool while it waits.
  5. 5. Awaiting each task again gives its string. They are already finished, so this does not wait.
  6. 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?
What is the request thread doing while GetOrderAsync waits for the database?
Show answer
.Result blocks the request thread until the task finishes, so a thread sits idle for every in-flight request. Under load the pool runs dry and requests queue. Fix: make the action <code>async Task&lt;IActionResult&gt;</code> and use <code>await _service.GetOrderAsync(id)</code>.

Explain like I'm 5

When you put bread in the toaster, you do not stand there staring at it. You make tea, and when it pops, you come back for it. Await means do something else while you wait, then come back.

Fun fact

ASP.NET Core removed the old synchronization context on purpose. That is why .Result rarely deadlocks there, but it still blocks a thread and can starve the pool.

Hands-on challenge

Say it out loud in 30 seconds: "What does await actually do, and why is calling .Result on a task a problem?" Then check: did you mention releasing the thread, the thread pool, and starvation or deadlock?

More resources

Open interactive version (quiz + challenge) Download the whole course as PDF / Word

Lock it in

← Back to course: .NET + React Playbook