Async State Machine Decomposition (IAsyncStateMachine)
1Concept
When a method is marked `async`, the Roslyn compiler synthesizes an internal `struct AsyncStateMachine : IAsyncStateMachine` containing a state variable, builder, and hoisted local variables.
2Architecture Diagram
async Task<int> FetchDataAsync() { int x = 10; await Task.Delay(10); return x; }
│
▼ Compiler Decomposition:
struct FetchDataStateMachine : IAsyncStateMachine
{
public int State; // -1 = Running, 0 = Awaiting, -2 = Finished
public int x; // Hoisted local variable
public AsyncTaskMethodBuilder<int> Builder;
public void MoveNext() { ... }
}3Code Example
C# 13 & .NET 9
using System;
using System.Threading.Tasks;
public class AsyncInternalsDemo
{
public static async Task<string> ExecuteWorkflowAsync()
{
int step = 1;
await Task.Yield(); // Forces async state machine continuation dispatch
step = 2;
return $"Completed Step {step}";
}
public static async Task Main()
{
string res = await ExecuteWorkflowAsync();
Console.WriteLine($"Workflow Result: {res}");
}
}4Expected Output
Workflow Result: Completed Step 2
5Key Takeaways
- ✓The async state machine struct is stack-allocated until an incomplete `await` forces heap boxing.
- ✓State number tracks progress across multiple await continuation points.
- ✓Avoid splitting methods into unnecessary tiny async helper methods.