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的更多相关文章

  1. C# async await and state machine

    Async Await and the Generated StateMachine https://www.codeproject.com/Articles/535635/Async-Await-a ...

  2. Async/Await FAQ

    From time to time, I receive questions from developers which highlight either a need for more inform ...

  3. 进阶篇:以IL为剑,直指async/await

    接上篇:30分钟?不需要,轻松读懂IL,这篇主要从IL入手来理解async/await的工作原理. 先简单介绍下async/await,这是.net 4.5引入的语法糖,配合Task使用可以非常优雅的 ...

  4. C# Async&Await

    在async和await之前我们用Task来实现异步任务是这样做的: static Task<string> GetBaiduHtmlTAP() { //创建一个异步Task对象,内部封装 ...

  5. C# Under the Hood: async/await (Marko Papic)

    https://www.markopapic.com/csharp-under-the-hood-async-await/ Async and await keywords came with C# ...

  6. async/await 内幕【译文】

    C# Under the Hood: async/await 原文地址:https://www.markopapic.com/csharp-under-the-hood-async-await/ 前言 ...

  7. 【译】Async/Await(三)——Aysnc/Await模式

    原文标题:Async/Await 原文链接:https://os.phil-opp.com/async-await/#multitasking 公众号: Rust 碎碎念 翻译 by: Praying ...

  8. C# 中 async/await 调用传统 Begin/End 异步方法

    最近在改进园子的图片上传程序,希望实现用户上传图片时同时将图片文件保存在三个地方:1)服务器本地硬盘:2)又拍云:3)阿里云OSS.并且在保存时使用异步操作. 对于异步保存到本地硬盘,只需用 Stea ...

  9. Async/Await - Best Practices in Asynchronous Programming z

    These days there’s a wealth of information about the new async and await support in the Microsoft .N ...

  10. async/await 的基本实现和 .NET Core 2.1 中相关性能提升

    前言 这篇文章的开头,笔者想多说两句,不过也是为了以后再也不多嘴这样的话. 在日常工作中,笔者接触得最多的开发工作仍然是在 .NET Core 平台上,当然因为团队领导的开放性和团队风格的多样性(这和 ...

随机推荐

  1. maven jar包新版本检测工具推荐

    为什么需要 经常使用maven来构建项目的朋友,应该遇到过类似这样的情况:项目通过maven引入了很多jar包,随着时间推移,这些jar包都有了更优的新版本出来,想升级,但又觉得很繁琐.主要是因为两方 ...

  2. gird 布局控制元素都显示在一行

    gird 布局控制元素都显示在一行 <ul class="list"> <li v-for="(li, index) in list" :ke ...

  3. 日志服务化&可视化&统计化

    概述: ELK是Elasticsearch(简称es).Logstash.Kibana的简称,这三者是核心套件,但并非全部. Filebeat 日志采集工具  Logstash数据处理引擎   ela ...

  4. python快速制作可视化报表

  5. 关于安装hadoop时在centos上修改主机名总是不成功

    按照老师给的文件和网上搜的代码改了很多次,比如改/etc/sysconfig/network这些,无论改几次都没用,找了个帖子,说可能是因为 CentOS7版本由于与之前版本变化比较大,修改主机名的方 ...

  6. 【文献阅读】Automatic berthing for an underactuated unmanned surface vehicle: A real-time motion planning approach

    (1)文章工作 This paper presents Extended Dynamic Window Approach (EDWA) for the automatic berthing of an ...

  7. NSQ(6)-nsq相关策略

    1:nsq的流量控制 RDY 消息中间件的实现无非两种套路,一种让客户端pull,典型的比如kafka便是如此,而另一种则是push,也就是让客户端不需要做任何操作,只需要做好conn便可以源源不断收 ...

  8. oracle学习之undo读一致性及undo表空间设置大小

    1.一致性读和事务 一个事务开始以后,分配undo段头事务表和undo块,事务表指向undo块,数据块中有事务槽,底下有数据行,数据块事务槽中事务ID指向事务表,事务表指向undo块,数据块事务槽也指 ...

  9. 【Anaconda】Jupyter 中添加 Anaconda 环境

    两种方法: 1. 安装 nb_conda_kernels,将所有 conda 环境同步至 Jupyter Notebook,参考『Jupyter notebook选择conda环境 - 简书』. 2. ...

  10. disp

    str = sprintf( 'Best Cross Validation MSE = %g Best c = %g Best g = %g',bestmse,bestc,bestg); disp(s ...