C# Async / Await State Machine

2023-03-12,,

The async/await keywords in C# are very much syntactical sugar that the compiler will use to generate the real code working behind async/await.

c#中的async/await关键字是语法糖,编译器使用它们来生成背后工作的真正代码.

The async/await pattern is not a core part of the language, but is instead implemented with a state machine. Each async method will be translated into a state machine and then the calling method will use this state machine to execute business logic.

async/await模式不是语言的核心部分,核心部分是通过状态机实现的。每个异步方法都将被转换为一个状态机,然后调用方法将使用这个状态机来执行业务逻辑。

示例代码

给定以下方法:

1 public async Task PrintAndWait(TimeSpan delay, int arg2)
2 {
3 Console.WriteLine("Before first delay");
4 await Task.Delay(delay);
5 Console.WriteLine("Between delays");
6 await Task.Delay(delay);
7 Console.WriteLine("After second delay");
8 }

经过编译后,上面的方法会变成如下:

 1 [AsyncStateMachine(typeof(<PrintAndWait>d__0))]
2 [DebuggerStepThrough]
3 public Task PrintAndWait(TimeSpan delay, int arg2)
4 {
5 <PrintAndWait>d__0 stateMachine = new <PrintAndWait>d__0();
6 stateMachine.<>4__this = this;
7 stateMachine.delay = delay;
8 stateMachine.arg2 = arg2;
9 stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
10 stateMachine.<>1__state = -1;
11 AsyncTaskMethodBuilder <>t__builder = stateMachine.<>t__builder;
12 <>t__builder.Start(ref stateMachine);
13 return stateMachine.<>t__builder.Task;
14 }

我们对上面的代码整理、简化成如下代码:

[AsyncStateMachine(typeof(PrintAndWaitStateMachine))]
[DebuggerStepThrough]
public Task PrintAndWait(TimeSpan delay, int arg2)
{
PrintAndWaitStateMachine stateMachine = new PrintAndWaitStateMachine()
{
Delay = delay,
Arg2 = arg2,
Builder = AsyncTaskMethodBuilder.Create(),
State = -1
};
stateMachine.Builder.Start(ref stateMachine);
return stateMachine.Builder.Task;
}

观察上面的代码,我们发现,asyncawait修饰符已被删除,方法主体已被转换为创建和启动状态机的PrintAndWaitStateMachine。同时编译器还将生成PrintAndWaitStateMachine类。生成的PrintAndWaitStateMachine类如下:

 1 [CompilerGenerated]
2 private sealed class <PrintAndWait>d__0 : IAsyncStateMachine
3 {
4 public int <>1__state;
5 public AsyncTaskMethodBuilder <>t__builder;
6 public TimeSpan delay;
7 public int arg2;
8 public C <>4__this;
9 private TaskAwaiter <>u__1;
10
11 private void MoveNext()
12 {
13 int num = <>1__state;
14 try
15 {
16 TaskAwaiter awaiter;
17 TaskAwaiter awaiter2;
18 if (num != 0)
19 {
20 if (num == 1)
21 {
22 awaiter = <>u__1;
23 <>u__1 = default(TaskAwaiter);
24 num = (<>1__state = -1);
25 goto IL_00ef;
26 }
27 Console.WriteLine("Before first delay");
28 awaiter2 = Task.Delay(delay).GetAwaiter();
29 if (!awaiter2.IsCompleted)
30 {
31 num = (<>1__state = 0);
32 <>u__1 = awaiter2;
33 <PrintAndWait>d__0 stateMachine = this;
34 <>t__builder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine);
35 return;
36 }
37 }
38 else
39 {
40 awaiter2 = <>u__1;
41 <>u__1 = default(TaskAwaiter);
42 num = (<>1__state = -1);
43 }
44 awaiter2.GetResult();
45 Console.WriteLine("Between delays");
46 awaiter = Task.Delay(delay).GetAwaiter();
47 if (!awaiter.IsCompleted)
48 {
49 num = (<>1__state = 1);
50 <>u__1 = awaiter;
51 <PrintAndWait>d__0 stateMachine = this;
52 <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
53 return;
54 }
55 goto IL_00ef;
56 IL_00ef:
57 awaiter.GetResult();
58 Console.WriteLine("After second delay");
59 }
60 catch (Exception exception)
61 {
62 <>1__state = -2;
63 <>t__builder.SetException(exception);
64 return;
65 }
66 <>1__state = -2;
67 <>t__builder.SetResult();
68 }
69
70 void IAsyncStateMachine.MoveNext()
71 {
72 //ILSpy generated this explicit interface implementation from .override directive in MoveNext
73 this.MoveNext();
74 }
75
76 [DebuggerHidden]
77 private void SetStateMachine(IAsyncStateMachine stateMachine) { }
78
79 void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
80 {
81 //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
82 this.SetStateMachine(stateMachine);
83 }
84 }

对上述代码整理、简化成如下代码:

 1 [CompilerGenerated]
2 class PrintAndWaitStateMachine : IAsyncStateMachine
3 {
4 public int State;
5 public AsyncTaskMethodBuilder Builder;
6 public TimeSpan delay;
7 public int arg2;
8
9 private TaskAwaiter _awaiter;
10
11 void IAsyncStateMachine.MoveNext()
12 {
13 int num = State;
14 try
15 {
16 TaskAwaiter awaiter;
17 TaskAwaiter awaiter2;
18 if (num != 0)
19 {
20 if (num == 1)
21 {
22 awaiter = _awaiter;
23 _awaiter = default(TaskAwaiter);
24 num = (State = -1);
25 goto IL_00ef;
26 }
27 Console.WriteLine("Before first delay");
28 awaiter2 = Task.Delay(delay).GetAwaiter();
29 if (!awaiter2.IsCompleted)
30 {
31 num = (State = 0);
32 _awaiter = awaiter2;
33 PrintAndWaitStateMachine stateMachine = this;
34 Builder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine);
35 return;
36 }
37 }
38 else
39 {
40 awaiter2 = _awaiter;
41 _awaiter = default(TaskAwaiter);
42 num = (State = -1);
43 }
44 awaiter2.GetResult();
45 Console.WriteLine("Between delays");
46 awaiter = Task.Delay(delay).GetAwaiter();
47 if (!awaiter.IsCompleted)
48 {
49 num = (State = 1);
50 _awaiter = awaiter;
51 PrintAndWaitStateMachine stateMachine = this;
52 Builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
53 return;
54 }
55 goto IL_00ef;
56 IL_00ef:
57 awaiter.GetResult();
58 Console.WriteLine("After second delay");
59 }
60 catch (Exception exception)
61 {
62 State = -2;
63 Builder.SetException(exception);
64 return;
65 }
66 State = -2;
67 Builder.SetResult();
68 }
69
70 void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
71 {
72 this.Builder.SetStateMachine(stateMachine);
73 }
74 }

delayarg2参数现在是状态机类的字段,原始PrintAndWait()方法中的逻辑现在在状态机的MoveNext()方法中。没有了async修饰符,很明显就不存在IL/CLR级别的"async",编译器仅仅只是在转换代码。

状态机(The State Machine)

生成的状态机,它的工作原理是通过保存当前的方法的上下文(context)即状态(State)以便于执行完毕耗时的任务后,状态机还可以继续运行下去。

PrintAndWaitStateMachine.MoveNext()方法的内部,我们可以看见几个检查点,分别是:代表当前状态的(num)值和调用方法Builder.AwaitUnsafeOnCompleted()

 1 MoveNext()
2 {
3 int num = State;
4 try
5 {
6 TaskAwaiter awaiter;
7 TaskAwaiter awaiter2;
8 if (num != 0)
9 {
10 if (num == 1)
11 {
12 awaiter = _awaiter;
13 _awaiter = default(TaskAwaiter);
14 num = (State = -1);
15 goto IL_00ef;
16 }
17 Console.WriteLine("Before first delay");
18 awaiter2 = Task.Delay(delay).GetAwaiter();
19 if (!awaiter2.IsCompleted)
20 {
21 num = (State = 0);
22 _awaiter = awaiter2;
23 PrintAndWaitStateMachine stateMachine = this;
24 Builder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine);
25 return;
26 }
27 }
28 else
29 {
30 awaiter2 = _awaiter;
31 _awaiter = default(TaskAwaiter);
32 num = (State = -1);
33 }
34 awaiter2.GetResult();
35 Console.WriteLine("Between delays");
36 awaiter = Task.Delay(delay).GetAwaiter();
37 if (!awaiter.IsCompleted)
38 {
39 num = (State = 1);
40 _awaiter = awaiter;
41 PrintAndWaitStateMachine stateMachine = this;
42 Builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
43 return;
44 }
45 goto IL_00ef;
46 IL_00ef:
47 awaiter.GetResult();
48 Console.WriteLine("After second delay");
49 }
50 catch (Exception exception)
51 {
52 State = -2;
53 Builder.SetException(exception);
54 return;
55 }
56 State = -2;
57 Builder.SetResult();
58 }

生成的代码被原始方法中的每个await关键字分割成片段。因此,该方法将一直执行到第一个等待器(await task.delay(delay)),如果这个等待器尚未完成,它将调用AwaitUnsafeOnCompleted(),传入长时间运行的任务的等待器引用和对当前状态机的引用。AwaitUnsafeOnCompleted()做的事情包括调度状态机在指定的等待器完成时继续执行下一个操作;这可以被认为类似于回调或唤醒事件。

 在AwaitUnsafeOnCompleted()方法被调用后,我们返回(或放弃对调用方法的控制权),线程被释放去做其他事情(可能是更新UI)。当等待器完成时,“Wake Up Event”被触发,MoveNext()方法再次执行,这次它已经有了一个present State,所以它将能够移动到下一个等待任务。在上面的代码中,它将遵循相同的流程来完成第二个await Task.Delay(delay)调用。
 

状态机中的状态(num/State的值)

-2

方法的结果已计算或已抛出;我们可以真正地返回(return),且再也不回来;

-1

"await Task.Delay(delay)"的起始位置。

如果任务立即完成了,或者已经完成了,就继续。

如果它还没有完成,则等待它完成,然后返回。

0~N

正整数,这些是根据原始方法中使用的await关键字的数量生成的。在上面的代码中,只使用了2个await,因此上述代码的状态机中出现了状态1和2

C# Async / Await State Machine的相关教程结束。

《C# Async / Await State Machine.doc》

下载本文的Word格式文档,以方便收藏与打印。