How Async works
async/await keywords
Methods marked with async
may use the await
keyword. The async
keyword was
introduced for compatibility with older code, which could’ve used await
as a
name of variables or other things. This way, if such older code gets upgraded to
.NET > 4.5 (Framework), it will continue to work. Only after the async
keyword
is added, the await
becomes a keyword in a method.
State Machine
The code that uses async/await is just a syntactic sugar. In reality, that code
gets turned into a state machine where state transitions happen whenever await
is encountered. The async
methods get turned into classes (or structs in
Release configuration) representing those state machines.
Here’s an example:
Before:
After:
There are a few “actors” that work together to make the async/await flow work:
Task
- the type that represents something that will complete in the futureTaskCompletionSource
- the type that can create a task, and set its completionAsyncMethodBuilder
-Task
is not the only thing that we can await. There are other built-in “awaitables” (likeValueTask
). We can also create them ourselves. Every awaitable type needs its own AsyncMethodBuilder. It is responsible for the lifecycle of the async method that returns a given awaitable type.INotifyCompletion
(awaiter) - the type that sets continuations on awaitables. Every awaitable type needs its own implementation of this interface.IAsyncStateMachine
- the code that gets generated in place of our async/await methods gets put into a class or struct that implements this interface.ExecutionContext
- this type enablesAsyncLocal
to store ambient data in async workloads.