Skip to content
Lesson 1 of 7

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:

  • async methods don't run on threads. They suspend and resume — possibly on different threads.
  • await doesn'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 contextConfigureAwait(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 a void method — not await-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.

Up nextIDisposable and usingAsync Patterns and Disposal

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…