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. 批量统一调整PDF页面尺寸大小

    先合并所有PDF页面到同一个文件 使用福昕PDF编辑版打开合并后的文件,选择打印功能,选择福昕虚拟打印机,打印处理器选择"比例"模式 打印并保存文件 检查新文件各页面是否大小一致并 ...

  2. mysql报错:MySQL server has gone away

    一.报错提示: 二.报错原因: 原因一: 一种可能是发送的 SQL 语句太长,以致超过了 max_allowed_packet 的大小,如果是这种原因,你只要修改 my.cnf,加大 max_allo ...

  3. input file 图片上传前预览

    1.获取到这个File对象之后就可以获取到上传文件的一些属性,比如:lastModified(代表文件的修改日期,而非上传日期).name.size(单位是b).type(例如图片就是"im ...

  4. mybatisplus SQL一对多

    https://blog.csdn.net/Isyoubao/article/details/122212113 重点:<collection property="nspSchedul ...

  5. linux:day01 计算机基础 随堂笔记 马

    本课程内容目录(前30天) 一,计算机基础 1,机械硬盘是比较慢的,如果有条件的话,还是换成固态硬盘有个120G就够了,价钱大概500G 700元 2,视频从一台机器拷贝到另外一台机器,复制的时候要限 ...

  6. hive安装准备 (mysql8.0安装)

    1.先准备好安装的所需材料 2.开始准备安装 先安装mysql 解压命令:tar -xvJf  (注意:这里' j '是大写) 解压后改名: mv mysql-8.0.24-linux-glibc2. ...

  7. SVN安装配置手册

    1.官网下载相关的服务端的安装包 SVN(版本控制器): SVN-客户端:TortoiseSVN SVN-服务端:VisualSVN 下载地址: TortoiseSVN:https://tortois ...

  8. fatal error: openssl/ssl.h: No such file or director

    $ sudo apt-get install libssl-dev

  9. SPI接口(续二)

    接下来看SPI接收器数据寄存器RXDAT,下表是它的全部位结构,其地址分别为0x40058014(SPI0).0x4005C014(SPI1). (1)第0到15位(RXDAT)为接收器数据,它包含接 ...

  10. ETL常用的三种工具介绍及对比Datastage,Informatica和Kettle

    https://blog.csdn.net/qq_34901049/article/details/103676959 大数据量下Informatica与Datastage的处理速度是比较快的,比较稳 ...