Imagine you're going to a bank to file some paperwork.
You hand your files to a window, then you have two choices:
- Stand at the window, never leave until you get the result — even if an earthquake hits.
- Wait in the lobby and do your own thing, until you are called.
Option 2 is what await does. Option 1 is what .Result does. The trick is in which window calls you back — and that's where .ConfigureAwait(false) comes in.
The mental model
- Request (you) = the calling code.
- Bank vault = the async operation doing its work in the background.
- Window / clerk = a thread that delivers the result back to you.
- SynchronizationContext = the rule that says which window must deliver your result (e.g. "always window #3").
In a UI app (WPF, WinForms) or classic ASP.NET, there's always an assigned window — it's the UI thread or the request thread. In ASP.NET Core or a console app, there is no assignment — any free clerk can deliver the result.
The three options side by side
// 1. await — go wait in the lobby, come back to my ASSIGNED window
var result = await GetDataAsync();
// 1. await — go wait in the lobby, come back to my ASSIGNED window, .ConfigureAwait(true) is not neccessary
var result = await GetDataAsync().ConfigureAwait(true);
// 2. await + ConfigureAwait(false) — go wait in the lobby, come back to ANY free window
var result = await GetDataAsync().ConfigureAwait(false);
// 3. .Result — stand at the window, refuse to move until I have the receipt, even the earthquake come
var result = GetDataAsync().Result;
-
await(default) = "I leave the lobby; when ready, deliver through my assigned window." -
await ... .ConfigureAwait(false)= "I leave the lobby; when ready, deliver through any free window." -
.Result= "I block the lobby; I will not move until the receipt is in my hand."
When to actually use it
- In your own ASP.NET Core services and controllers: skip it. ASP.NET Core has no assigned windows —
.ConfigureAwait(false)does nothing. - In library code (NuGet packages, shared infrastructure): use it everywhere as defensive armor — you don't know if a caller might block on
.Resultfrom WPF or classic ASP.NET. - In WPF / WinForms / classic ASP.NET app code: use
await(default) when you need to touch the UI or request state after the await; switch to.ConfigureAwait(false)only for work that doesn't need the original context. -
Never mix
.Result(or.Wait()) with async code unless you have a very specific reason — that's the only situation where this whole discussion matters.
TL;DR
-
await= leave the lobby, come back through my window. -
.ConfigureAwait(false)= leave the lobby, come back through any window. -
.Result= block the lobby until the receipt arrives. - Deadlock =
.Resultblocks the very window the continuation insists on using. - ASP.NET Core has no assigned windows, so the whole problem doesn't exist there.
Top comments (0)