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:
using System;using System.Threading.Tasks;
public class MyClass{ public async Task MyAsyncMethod(int someValue) { Console.WriteLine("Starting MyAsyncMethod");
await Task.Delay(1000);
Console.WriteLine("After 1st await");
await Task.Delay(someValue);
Console.WriteLine("After 2nd await");
await DoSomethingAsync();
Console.WriteLine("After 3rd await"); }
private async Task DoSomethingAsync() { await Task.Delay(1000); }}
After:
using System;using System.Diagnostics;using System.Reflection;using System.Runtime.CompilerServices;using System.Runtime.InteropServices;using System.Security;using System.Security.Permissions;using System.Threading.Tasks;
[assembly: CompilationRelaxations(8)][assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)][assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)][assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)][assembly: AssemblyVersion("0.0.0.0")][module: UnverifiableCode][module: RefSafetyRules(11)]
[NullableContext(1)][Nullable(0)]public class MyClass{ [StructLayout(LayoutKind.Auto)] [CompilerGenerated] private struct <DoSomethingAsync>d__1 : IAsyncStateMachine { public int <>1__state;
public AsyncTaskMethodBuilder <>t__builder;
private TaskAwaiter <>u__1;
private void MoveNext() { int num = <>1__state; try { TaskAwaiter awaiter; if (num != 0) { awaiter = Task.Delay(1000).GetAwaiter(); if (!awaiter.IsCompleted) { num = (<>1__state = 0); <>u__1 = awaiter; <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this); return; } } else { awaiter = <>u__1; <>u__1 = default(TaskAwaiter); num = (<>1__state = -1); } awaiter.GetResult(); } catch (Exception exception) { <>1__state = -2; <>t__builder.SetException(exception); return; } <>1__state = -2; <>t__builder.SetResult(); }
void IAsyncStateMachine.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext this.MoveNext(); }
[DebuggerHidden] private void SetStateMachine(IAsyncStateMachine stateMachine) { <>t__builder.SetStateMachine(stateMachine); }
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine this.SetStateMachine(stateMachine); } }
[StructLayout(LayoutKind.Auto)] [CompilerGenerated] private struct <MyAsyncMethod>d__0 : IAsyncStateMachine { public int <>1__state;
public AsyncTaskMethodBuilder <>t__builder;
public int someValue;
[Nullable(0)] public MyClass <>4__this;
private TaskAwaiter <>u__1;
private void MoveNext() { int num = <>1__state; MyClass myClass = <>4__this; try { TaskAwaiter awaiter; switch (num) { default: Console.WriteLine("Starting MyAsyncMethod"); awaiter = Task.Delay(1000).GetAwaiter(); if (!awaiter.IsCompleted) { num = (<>1__state = 0); <>u__1 = awaiter; <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this); return; } goto IL_0082; case 0: awaiter = <>u__1; <>u__1 = default(TaskAwaiter); num = (<>1__state = -1); goto IL_0082; case 1: awaiter = <>u__1; <>u__1 = default(TaskAwaiter); num = (<>1__state = -1); goto IL_00ec; case 2: { awaiter = <>u__1; <>u__1 = default(TaskAwaiter); num = (<>1__state = -1); break; } IL_00ec: awaiter.GetResult(); Console.WriteLine("After 2nd await"); awaiter = myClass.DoSomethingAsync().GetAwaiter(); if (!awaiter.IsCompleted) { num = (<>1__state = 2); <>u__1 = awaiter; <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this); return; } break; IL_0082: awaiter.GetResult(); Console.WriteLine("After 1st await"); awaiter = Task.Delay(someValue).GetAwaiter(); if (!awaiter.IsCompleted) { num = (<>1__state = 1); <>u__1 = awaiter; <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this); return; } goto IL_00ec; } awaiter.GetResult(); Console.WriteLine("After 3rd await"); } catch (Exception exception) { <>1__state = -2; <>t__builder.SetException(exception); return; } <>1__state = -2; <>t__builder.SetResult(); }
void IAsyncStateMachine.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext this.MoveNext(); }
[DebuggerHidden] private void SetStateMachine(IAsyncStateMachine stateMachine) { <>t__builder.SetStateMachine(stateMachine); }
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine this.SetStateMachine(stateMachine); } }
[AsyncStateMachine(typeof(<MyAsyncMethod>d__0))] public Task MyAsyncMethod(int someValue) { <MyAsyncMethod>d__0 stateMachine = default(<MyAsyncMethod>d__0); stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create(); stateMachine.<>4__this = this; stateMachine.someValue = someValue; stateMachine.<>1__state = -1; stateMachine.<>t__builder.Start(ref stateMachine); return stateMachine.<>t__builder.Task; }
[AsyncStateMachine(typeof(<DoSomethingAsync>d__1))] private Task DoSomethingAsync() { <DoSomethingAsync>d__1 stateMachine = default(<DoSomethingAsync>d__1); stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create(); stateMachine.<>1__state = -1; stateMachine.<>t__builder.Start(ref stateMachine); return stateMachine.<>t__builder.Task; }}
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.