LyraLearn AI Learning Platform
Exams
← Module 1 Β· Modern C# for MVC Developers
🎧 Listen

Async/Await Done Right

Web servers live or die by threads. Every request that sits waiting on SQL Server or an HTTP call while holding a thread is a thread that can't serve anyone else. async/await releases the thread during the wait β€” that's the whole point. It is about scalability, not speed: an awaited query takes just as long, but the server survives load spikes.

Task, async, and "async all the way"

An async method returns Task (no result) or Task<T> and uses await at each point where it would otherwise block. In MVC this composes cleanly top to bottom:

public async Task<IActionResult> Details(int id)
{
    var order = await _orderService.GetOrderAsync(id);
    return View(order);
}

The cardinal rule is async all the way down: if the data layer is async, the service and the controller above it must be too. The moment someone bridges the gap with .Result or .Wait(), you get sync-over-async β€” a thread parked doing nothing, thread-pool starvation under load, and in legacy code, deadlocks. Never write async void except for event handlers; exceptions from async void can't be caught by the caller and can take down the process.

Deadlocks and the ConfigureAwait story

You'll hear war stories about "the classic ASP.NET deadlock." Know the punchline: ASP.NET Core has no SynchronizationContext. After an await, execution resumes on any thread-pool thread β€” there is no per-request context for a blocked .Result to fight over, so that deadlock simply doesn't exist here, and ConfigureAwait(false) in application code buys you nothing. Don't sprinkle it through controllers and services. It's still a reasonable convention inside reusable libraries (which may be consumed by UI frameworks that do have a context). Legacy note: the deadlock is real if you ever touch an old System.Web app β€” there, never block on tasks, ever. Either way, .Result in a controller remains a code-review flag: even without the deadlock, it's sync-over-async and wastes the thread you were trying to free.

CancellationToken: stop work nobody wants

When a user closes the tab mid-request, why should your 20-second report query keep running? Accept a CancellationToken in your async methods and pass it through to EF (ToListAsync(ct)) and HttpClient. Add a CancellationToken parameter to the action and model binding wires it to HttpContext.RequestAborted automatically β€” abandoned requests then cancel the whole chain. The same token plumbing also serves timeouts you impose yourself via CancellationTokenSource. Design your service signatures with the token from day one; adding it later touches every layer.

🧠 Quiz yourself on this lesson →

Ask the AI Tutor

Grounded in the course lessons β€” it cites its sources and says when it doesn't know.