Skip to content
async/await Internals
step 1/5

Reading — step 1 of 5

Learn

~1 min readAsync 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.

Discussion

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

Sign in to post a comment or reply.

Loading…