Step 1 of 5 · Reading · ~2 min
Learn
Async Patterns and Disposal
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.
What this judge's compiler will not accept
The exercises here run on an older Mono mcs, which predates async Task Main (C# 7.1).
Declaring one does not fail with a friendly message -- the compiler simply stops recognising
your entry point:
static async Task Main() { ... }
// warning CS0028: `Program.Main()' has the wrong signature to be an entry point
// error CS5001: does not contain a static `Main' method suitable for an entry point
So the exercise keeps a synchronous Main and blocks once at the very top:
static void Main() {
Console.WriteLine(SumAllAsync(nums).GetAwaiter().GetResult());
}
That is the one place blocking on a task is defensible: the process has nothing else to do, and there is no synchronization context to deadlock against. The same call one frame further in is the sync-over-async bug listed above.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…