async/await. Where is continuation of awaitable part of method performed?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
When a C# method hits await, the rest of the method does not simply disappear and restart somewhere random later. The compiler splits the method into a state machine, and the continuation runs wherever the captured context or scheduler says it should run after the awaited operation completes.
What actually happens at await
Consider this method:
At the await, C# does not block the thread. Instead it:
- checks whether the awaited task is already complete
- if not, stores the current state of the method
- registers the remainder of the method as a continuation
- returns control to the caller
When the awaited task finishes, that continuation is scheduled to run.
Where the continuation runs
The short answer is: it depends on the current context.
In environments with a SynchronizationContext, such as UI frameworks, await usually captures that context. That means the continuation tries to resume on the original UI thread so code after the await can safely interact with UI state.
In environments without a special context, such as many console apps and ASP.NET Core request handlers, the continuation usually resumes on a thread pool thread.
So the continuation is not tied to "the same thread" as a guarantee. It is tied to the scheduling rules of the captured context, if one exists.
UI example versus server example
In a WinForms or WPF app:
- '
awaitoften captures the UI context' - the continuation resumes on the UI thread
In a console app:
- there is usually no UI context to capture
- the continuation resumes wherever the task scheduler chooses, commonly a thread pool thread
That difference explains why UI code can update controls after an await but library code should not assume a particular thread.
The role of ConfigureAwait(false)
If you do not want to resume on the captured context, use ConfigureAwait(false):
This tells the awaiter not to marshal the continuation back to the original context. That is common in library code where there is no reason to return to a UI thread or request context.
It does not mean "run on a background thread." It means "do not require the original context for the continuation."
Why this matters for deadlocks and performance
Understanding continuation placement helps explain two common problems.
First, blocking on async code with .Result or .Wait() can deadlock in context-sensitive environments. The caller blocks the thread while the continuation is waiting to get back onto that same context.
Second, unnecessary context capture adds overhead. In library code that does not need it, ConfigureAwait(false) can remove that extra scheduling requirement.
A simple mental model
The safest mental model is:
- '
awaitpauses the method logically, not the thread physically' - the rest of the method becomes a continuation
- that continuation runs on the captured context if one is required
- otherwise it runs on a scheduler-selected thread, often from the thread pool
That model is much more accurate than "await always resumes on the same thread."
Common Pitfalls
One common mistake is assuming await creates a new thread. It usually does not. It coordinates continuation scheduling around an existing asynchronous operation.
Another issue is assuming the continuation always runs on the same thread. That is often true in UI applications, but it is not a general language guarantee.
People also misuse ConfigureAwait(false) by treating it as a performance switch to sprinkle everywhere without understanding the code’s threading needs. In UI code, resuming off the UI context can break code that touches controls.
Finally, avoid mixing async code with blocking waits. Deadlocks caused by captured contexts are one of the oldest and most common async bugs in C#.
Summary
- '
awaitturns the rest of the method into a continuation inside a compiler-generated state machine.' - The continuation runs according to the captured context or scheduler.
- UI apps usually resume on the UI thread, while console and server apps often resume on thread pool threads.
- '
ConfigureAwait(false)skips context capture when the continuation does not need it.' - '
awaitis about scheduling continuation, not about creating threads directly.'

