Reading — step 1 of 5
Learn
async/await is not lightweight threads — it's a state-machine transformation by the compiler.
Given this:
async Task<int> ProcessAsync(int x) {
var a = await FetchA();
var b = await FetchB(a);
return a + b;
}
The compiler generates roughly:
class StateMachine {
int state;
int x, a, b;
TaskCompletionSource<int> tcs;
TaskAwaiter<int> awaiter;
void MoveNext() {
switch (state) {
case 0:
awaiter = FetchA().GetAwaiter();
if (awaiter.IsCompleted) {
a = awaiter.GetResult();
goto case 1;
}
state = 1;
awaiter.OnCompleted(MoveNext);
return;
case 1:
a = awaiter.GetResult();
...
}
}
}
Each await is a yield point. The state machine resumes when the awaited task completes.
Crucial implications:
asyncmethods don't run on threads. They suspend and resume — possibly on different threads.awaitdoesn't block — it returns control to the caller. The thread can do other work.- For CPU-bound work, you still need threads. Use
Task.Run(() => CpuWork()).
ConfigureAwait(false) — important for libraries:
var result = await fetch().ConfigureAwait(false);
By default, await resumes on the captured synchronization context (UI thread for desktop apps, request context for ASP.NET classic). ConfigureAwait(false) skips that — resumes on any thread pool thread.
- App code — usually want default (so UI updates on UI thread)
- Library code — usually want
ConfigureAwait(false)(avoid deadlocks, faster)
ASP.NET Core no longer has a sync context — ConfigureAwait(false) matters less. But it's still standard library practice.
Common bugs:
async void— fire-and-forget; exceptions get swallowed. Avoid except for event handlers.Task.Result/Task.Wait()— sync-over-async. Deadlocks classic ASP.NET.- Forgetting to await — task runs but you don't see exceptions or results.
Task<T>returned from avoidmethod — notawait-able.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…