In .NET, async and await are syntactic sugar for a state machine that the C# compiler generates under the hood. Here’s how it works:
1. Compiler Transformation into a State Machine
When you mark a method as async, the C# compiler transforms it into a state machine, allowing it to handle asynchronous execution seamlessly.
For example, consider this simple asynchronous method:
public async Task<int> FetchDataAsync()
{
await Task.Delay(1000);
return 42;
}
The compiler transforms it into a state machine structure, roughly equivalent to:
public Task<int> FetchDataAsync()
{
var stateMachine = new FetchDataAsyncStateMachine();
stateMachine.MoveNext(); // Begin execution
return stateMachine.Task;
}
2. The State Machine Class
The compiler generates a struct implementing IAsyncStateMachine. This struct contains:
-
State tracking variable (e.g.,
_state) -
A builder object (
AsyncTaskMethodBuilder<T>), which helps in executing the method -
Fields to store local variables and awaitables
-
The MoveNext() method, which drives the execution
3. The MoveNext() Method
The MoveNext() method is where the logic happens. It works like a switch statement that jumps between states based on await points.
For example:
void MoveNext()
{
try
{
if (_state == -1) // Initial state
{
_taskAwaiter = Task.Delay(1000).GetAwaiter();
if (!_taskAwaiter.IsCompleted)
{
_state = 0; // Store state
_builder.AwaitOnCompleted(ref _taskAwaiter, ref this);
return;
}
}
// Resume execution after await
int result = 42;
_builder.SetResult(result);
}
catch (Exception ex)
{
_builder.SetException(ex);
}
}
4. Role of Task, TaskAwaiter, and Builder
-
Task.Delay(1000).GetAwaiter()returns aTaskAwaiter, which helps in checking if the task is completed. -
If not completed, execution is paused, and the continuation is scheduled.
-
_builder.SetResult(42);completes the task with the result.
5. Continuations and Threading
-
The
MoveNext()method gets scheduled to run on the captured SynchronizationContext (like UI thread for WPF, ASP.NET context, or thread pool). -
If the context doesn't matter,
ConfigureAwait(false)can be used to avoid overhead.
Summary
-
async/awaitis converted into a state machine. -
The method is split into multiple states.
-
The compiler generates
MoveNext()to handle execution flow. -
Tasks and awaiters manage asynchronous execution.
-
Continuations are scheduled based on context.