从状态机的角度 async 和 await 的实现原理
- 2023-01-13 湖南
本文字数:10983 字
阅读完需:约 36 分钟
一. 深度剖析
准备:
先给 VS 安装一个插件 ILSpy,这样更容易反编译代码进行查看,另外要注意反编译 async 和 await 的时候,要把 C#代码版本改为 4.0 哦。
1.什么是状态机
(1).含义:通常我们所说的状态机(State Machine)指的是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型,可以理解成一个状态转换图。(状态机是计算机科学的重要基础概念之一,也可以说是一种总结归纳问题的思想,应用范围非常广泛)
(2).例子:自动门有两个状态,open 和 closed ,closed 状态下,如果读取开门信号,那么状态就会切换为 open 。open 状态下如果读取关门信号,状态就会切换为 closed .
(3).涉及到 4 个相关概念:
A.状态(State):一个状态机至少包括两个状态.(例如上面自动门的例子,有 open 和 closed 两个状态。)
B.事件(Event):事件就是执行某个操作的触发条件或者口令.(对于自动门,“按下开门按钮”就是一个事件。)
C.动作(Action):事件发生以后要执行的动作,一个 action 对应一个函数.(事件是“按开门按钮”,动作是“开门”)
D.变换(Transition):从一个状态转换成另外一个状态.(“开门过程”就是一个变换。)
(4). C#的状态机提供了 IAsyncStateMachine 接口,里面有 MoveNext 和 SetStateMachine 方法处理相应业务.
2. 状态机分析
async 关键字标记方法是一个异步方法,编译器通过这个标记去改造这个方法体为创建状态机的方法。await 是关键字是为了实现状态机中的一个状态, 每当有一个 await,就会生成一个对应的状态。状态机就是根据这个状态,去一步步的调用异步委托,然后回调,包括状态机的解析。
(1).状态机的默认状态都是-1, 结束状态都是-2.
(2).每 await 一次就会产生一个 TaskAwaiter<int> awaiter; 改变状态机的状态, 当有多个 await 的时候,每个 await 都会改变状态机的状态,比如 改为 0,1,2,3,4 等等, 分别表示 代码中 await xxx 这句话执行完成。
(3).状态机的执行套路:
A. 首先创建一个 <xxx>d_num 的方法, xxx 代表方法名,num 可能是 0,1,2,3 等,实现 IAsyncStateMachine 接口。
B. 在 MoveNext 方法中, 源代码中每个 await xxxx 都会对应生成是一个 TaskAwaiter<int> awaiter,然后 xxxx.GetAwaiter()
C. 判断状态机是否执行完 if (!awaiter.IsCompleted),没有执行完的话走 <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); 代表释放当前线程
D. 执行完后走,<>s__1 = awaiter.GetResult(); 拿到返回值,继续走后面的代码。
(此处写的比较抽象,看下面 3 结合代码编译再分析)
3. 结合代码编译分析
前提:准备 1 个 Index 方法,我们把它当做主方法,在该方法里面调用 F1Async-F5Async 这五个方法. (要补充截图这里)
代码分享:
public class Home2Controller : Controller
{
/// <summary>
/// 该方法为主方法,用于调用下面的F1-F5的方法
/// </summary>
/// <returns></returns>
public async Task<IActionResult> Index()
{
await F1Async();
await F2Async();
await F3Async();
await F4Async();
await F5Async();
return View();
}
/// <summary>
/// 没有加async和await的方法
/// (也是一个计算密集型的异步方法,只是编译的时候本身不会被编译成状态机)
/// </summary>
/// <returns></returns>
public static Task<int> F1Async()
{
return Task.Run(() =>
{
return 2;
});
}
/// <summary>
/// 只要标记了async 就会被编译成状态机
/// 如果方法声明为 async,那么可以直接 return 具体的值,不再用创建Task,由编译器创建 Task:
/// </summary>
/// <returns></returns>
public static async Task<int> F2Async()
{
return 2;
}
/// <summary>
/// 计算密集型的异步方法
/// (方法本身也会被编译成状态机)
/// </summary>
/// <returns></returns>
public static async Task<int> F3Async()
{
return await Task.Run(() =>
{
return 2;
});
}
/// <summary>
/// I/O密集型的异步方法
/// </summary>
/// <returns></returns>
public async Task<int> F4Async()
{
AsyncDBContext context = new AsyncDBContext();
for (int i = 0; i < 10000; i++)
{
UserInfor uInfor = new UserInfor()
{
id = Guid.NewGuid().ToString("N"),
userName = "ypf",
addTime = DateTime.Now
};
await context.AddAsync(uInfor);
}
return await context.SaveChangesAsync();
}
/// <summary>
/// 没有创建状态机,但是new 了1个新的 task
/// </summary>
/// <returns></returns>
public static Task<int> F5Async()
{
//内部是new Task<TResult>(result)
return Task.FromResult(3);
}
}
(1).F1Async:没有加 async 和 await,但它本身也是一个计算密集型的异步方法,该方法本身不会被编译成状态机,但调用它的方法 Index 会被编译成状态机。
(2).F2Async:只加了 async,会生成状态机,但由于没有加 await 所以不会涉及到中间状态的变化,从-1 默认状态 变为 结束的-2 状态。
更多 C++后台开发技术点知识内容包括 C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,MongoDB,ZK,流媒体,音视频开发,Linux 内核,TCP/IP,协程,DPDK 多个高级知识点。
C/C++Linux服务器开发高级架构师/C++后台开发架构师免费学习地址
【文章福利】另外还整理一些C++后台开发架构师 相关学习资料,面试题,教学视频,以及学习路线图,免费分享有需要的可以点击领取
代码分享:
1 public class Home2Controller : Controller
2 {
3
4 /// <summary>
5 /// 该方法为主方法,用于调用下面的F1-F5的方法
6 /// </summary>
7 /// <returns></returns>
8 public async Task<IActionResult> Index()
9 {
10 await F1Async();
11 await F2Async();
12 await F3Async();
13 await F4Async();
14 await F5Async();
15
16 return View();
17 }
18
19 /// <summary>
20 /// 没有加async和await的方法
21 /// (也是一个计算密集型的异步方法,只是编译的时候本身不会被编译成状态机)
22 /// </summary>
23 /// <returns></returns>
24 public static Task<int> F1Async()
25 {
26 return Task.Run(() =>
27 {
28 return 2;
29 });
30 }
31
32 /// <summary>
33 /// 只要标记了async 就会被编译成状态机
34 /// 如果方法声明为 async,那么可以直接 return 具体的值
35 /// <returns></returns>
36 public static async Task<int> F2Async()
37 {
38 return 2;
39 }
40
41 /// <summary>
42 /// 计算密集型的异步方法
43 /// (方法本身也会被编译成状态机)
44 /// </summary>
45 /// <returns></returns>
46 public static async Task<int> F3Async()
47 {
48 return await Task.Run(() =>
49 {
50 return 2;
51 });
52 }
53
54 /// <summary>
55 /// I/O密集型的异步方法
56 /// </summary>
57 /// <returns></returns>
58 public async Task<int> F4Async()
59 {
60 AsyncDBContext context = new AsyncDBContext();
61 for (int i = 0; i < 10000; i++)
62 {
63 UserInfor uInfor = new UserInfor()
64 {
65 id = Guid.NewGuid().ToString("N"),
66 userName = "ypf",
67 addTime = DateTime.Now
68 };
69 await context.AddAsync(uInfor);
70 }
71 return await context.SaveChangesAsync();
72 }
73
74
75 /// <summary>
76 /// 没有创建状态机,但是new 了1个新的 task
77 /// </summary>
78 /// <returns></returns>
79 public static Task<int> F5Async()
80 {
81 //内部是new Task<TResult>(result)
82 return Task.FromResult(3);
83 }
84
85 }
核心代码剖析:
(3).F3Async:既有 async 也有 await (await 只有 1 个),该方法是使用了 Task.Run,我们把它归为计算型的异步方法。
代码分享:
[AsyncStateMachine(typeof(<F3Async>d__3))]
[DebuggerStepThrough]
public static Task<int> F3Async()
{
<F3Async>d__3 stateMachine = new <F3Async>d__3();
stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
stateMachine.<>1__state = -1;
AsyncTaskMethodBuilder<int> <>t__builder = stateMachine.<>t__builder;
<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
[CompilerGenerated]
private sealed class <F3Async>d__3 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder<int> <>t__builder;
private int <>s__1;
private TaskAwaiter<int> <>u__1;
private void MoveNext()
{
int num = <>1__state;
int result;
try
{
TaskAwaiter<int> awaiter;
if (num != 0)
{
awaiter = Task.Run(() => 2).GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (<>1__state = 0);
<>u__1 = awaiter;
<F3Async>d__3 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
}
else
{
awaiter = <>u__1;
<>u__1 = default(TaskAwaiter<int>);
num = (<>1__state = -1);
}
<>s__1 = awaiter.GetResult();
result = <>s__1;
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<>t__builder.SetResult(result);
}
void IAsyncStateMachine.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
this.MoveNext();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
this.SetStateMachine(stateMachine);
}
}
核心代码剖析:
(4).F4Async:既有 async 又有 await,且两个 await,两个 await 按照顺序执行。
代码分享:
[AsyncStateMachine(typeof(<F4Async>d__4))]
[DebuggerStepThrough]
public Task<int> F4Async()
{
<F4Async>d__4 stateMachine = new <F4Async>d__4();
stateMachine.<>4__this = this;
stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
stateMachine.<>1__state = -1;
AsyncTaskMethodBuilder<int> <>t__builder = stateMachine.<>t__builder;
<>t__builder.Start<<F4Async>d__4>(ref stateMachine);
return stateMachine.<>t__builder.get_Task();
}
[CompilerGenerated]
private sealed class <F4Async>d__4 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder<int> <>t__builder;
public Home2Controller <>4__this;
private AsyncDBContext <context>5__1;
private int <i>5__2;
private UserInfor <uInfor>5__3;
private int <>s__4;
private ValueTaskAwaiter<EntityEntry<UserInfor>> <>u__1;
private TaskAwaiter<int> <>u__2;
private void MoveNext()
{
int num = <>1__state;
int result;
try
{
ValueTaskAwaiter<EntityEntry<UserInfor>> awaiter;
if (num == 0)
{
awaiter = <>u__1;
<>u__1 = default(ValueTaskAwaiter<EntityEntry<UserInfor>>);
num = (<>1__state = -1);
goto IL_00e8;
}
if (num != 1)
{
<context>5__1 = new AsyncDBContext();
<i>5__2 = 0;
goto IL_010a;
}
TaskAwaiter<int> awaiter2 = <>u__2;
<>u__2 = default(TaskAwaiter<int>);
num = (<>1__state = -1);
goto IL_0188;
IL_00e8:
awaiter.GetResult();
<uInfor>5__3 = null;
<i>5__2++;
goto IL_010a;
IL_010a:
if (<i>5__2 < 10000)
{
<uInfor>5__3 = new UserInfor
{
id = Guid.NewGuid().ToString("N"),
userName = "ypf",
addTime = DateTime.Now
};
awaiter = ((DbContext)<context>5__1).AddAsync<UserInfor>(<uInfor>5__3, default(CancellationToken)).GetAwaiter();
if (!awaiter.get_IsCompleted())
{
num = (<>1__state = 0);
<>u__1 = awaiter;
<F4Async>d__4 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted<ValueTaskAwaiter<EntityEntry<UserInfor>>, <F4Async>d__4>(ref awaiter, ref stateMachine);
return;
}
goto IL_00e8;
}
awaiter2 = ((DbContext)<context>5__1).SaveChangesAsync(default(CancellationToken)).GetAwaiter();
if (!awaiter2.get_IsCompleted())
{
num = (<>1__state = 1);
<>u__2 = awaiter2;
<F4Async>d__4 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<int>, <F4Async>d__4>(ref awaiter2, ref stateMachine);
return;
}
goto IL_0188;
IL_0188:
<>s__4 = awaiter2.GetResult();
result = <>s__4;
}
catch (Exception exception)
{
<>1__state = -2;
<context>5__1 = null;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<context>5__1 = null;
<>t__builder.SetResult(result);
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
}
核心代码剖析:
(5).F5Async:没有 async 和 await,没有创建状态机,但是 new 了 1 个新的 task。
(6).Index:多个 await,通过 goto 一步一步跳转,按顺序执行。
代码分享:
[CompilerGenerated]
private sealed class <Index>d__0 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder<IActionResult> <>t__builder;
public Home2Controller <>4__this;
private TaskAwaiter<int> <>u__1;
private void MoveNext()
{
int num = <>1__state;
IActionResult result;
try
{
TaskAwaiter<int> awaiter5;
TaskAwaiter<int> awaiter4;
TaskAwaiter<int> awaiter3;
TaskAwaiter<int> awaiter2;
TaskAwaiter<int> awaiter;
switch (num)
{
default:
awaiter5 = F1Async().GetAwaiter();
if (!awaiter5.get_IsCompleted())
{
num = (<>1__state = 0);
<>u__1 = awaiter5;
<Index>d__0 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<int>, <Index>d__0>(ref awaiter5, ref stateMachine);
return;
}
goto IL_0091;
case 0:
awaiter5 = <>u__1;
<>u__1 = default(TaskAwaiter<int>);
num = (<>1__state = -1);
goto IL_0091;
case 1:
awaiter4 = <>u__1;
<>u__1 = default(TaskAwaiter<int>);
num = (<>1__state = -1);
goto IL_00f3;
case 2:
awaiter3 = <>u__1;
<>u__1 = default(TaskAwaiter<int>);
num = (<>1__state = -1);
goto IL_0155;
case 3:
awaiter2 = <>u__1;
<>u__1 = default(TaskAwaiter<int>);
num = (<>1__state = -1);
goto IL_01bd;
case 4:
{
awaiter = <>u__1;
<>u__1 = default(TaskAwaiter<int>);
num = (<>1__state = -1);
break;
}
IL_01bd:
awaiter2.GetResult();
awaiter = F5Async().GetAwaiter();
if (!awaiter.get_IsCompleted())
{
num = (<>1__state = 4);
<>u__1 = awaiter;
<Index>d__0 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<int>, <Index>d__0>(ref awaiter, ref stateMachine);
return;
}
break;
IL_0091:
awaiter5.GetResult();
awaiter4 = F2Async().GetAwaiter();
if (!awaiter4.get_IsCompleted())
{
num = (<>1__state = 1);
<>u__1 = awaiter4;
<Index>d__0 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<int>, <Index>d__0>(ref awaiter4, ref stateMachine);
return;
}
goto IL_00f3;
IL_0155:
awaiter3.GetResult();
awaiter2 = <>4__this.F4Async().GetAwaiter();
if (!awaiter2.get_IsCompleted())
{
num = (<>1__state = 3);
<>u__1 = awaiter2;
<Index>d__0 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<int>, <Index>d__0>(ref awaiter2, ref stateMachine);
return;
}
goto IL_01bd;
IL_00f3:
awaiter4.GetResult();
awaiter3 = F3Async().GetAwaiter();
if (!awaiter3.get_IsCompleted())
{
num = (<>1__state = 2);
<>u__1 = awaiter3;
<Index>d__0 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<int>, <Index>d__0>(ref awaiter3, ref stateMachine);
return;
}
goto IL_0155;
}
awaiter.GetResult();
result = <>4__this.View();
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<>t__builder.SetResult(result);
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
}
核心代码剖析:
4. 重点比较一下:F1Async 和 F3Async 的区别
(1).F1Async 和 F3Async 都是异步方法,在外层 Index 方法中调用的时候,都要加 await,就外层而言都不会卡主线程,外层方法都会被编译成状态机。
(2).从编译的角度而言 F1Async 方法本身不会被编译成状态机,F3Async 方法本身会被编译成状态机。
5. 再次扩展
(1).等待的时候要用 await xxxAsync, 而不要用 xxxAsync.wait() 和 .Result
(2).等待多个用 await Task.WhenAll 而不要用 Task.WaitAll
原因?
后者是同步写法啊,阻塞线程的,从上面的编译的源码可以看出来,没有 await 不会生成 TaskAwaiter<int> awaiter。
二. 几个用法
1. 异常捕获
代码 1
public static async void EmailAsync() {
List<string> addrs = new List<string>();
IEnumerable<Task> asyncOps = addrs.Select(addr => SendMailAsync(addr));
try {
await Task.WhenAll(asyncOps);
} catch (AggregateException ex) {
// 可以通过 InnerExceptions 来得到内部返回的异常
var exceptions = ex.InnerExceptions;
// 也可以使用 Handle 对每个异常进行处理
ex.Handle(innerEx => {
// 此处的演示仅仅为了说明 ex.Handle 可以对异常进行单独处理
// 实际项目中不一定会抛出此异常
if (innerEx is OperationCanceledException oce) {
// 对 OperationCanceledException 进行单独的处理
return true;
} else if (innerEx is UnauthorizedAccessException uae) {
// 对 UnauthorizedAccessException 进行单独处理
return true;
}
return false;
});
}
}
代码 2
public static async void EmailAsync() {
List<string> addrs = new List<string>();
IEnumerable<Task> asyncOps = addrs.Select(addr => SendMailAsync(addr));
try {
await Task.WhenAll(asyncOps);
} catch (AggregateException ex) {
// 此处可以针对每个任务进行更加具体的管理
foreach (Task<string> task in asyncOps) {
if (task.IsCanceled) {
}else if (task.IsFaulted) {
}else if (task.IsCompleted) {
}
}
}
}
代码 3
try
{
HttpClient hc = new HttpClient();
var task1 = hc.GetStringAsync(textBox1.Text);
var task2 = hc.GetStringAsync(textBox2.Text);
var task3 = hc.GetStringAsync(textBox3.Text);
Task.WaitAll(task1, task2, task3);
label1.Text = task1.Result.Length.ToString();
label2.Text = task2.Result.Length.ToString();
label3.Text = task3.Result.Length.ToString();
}
catch (AggregateException ae)
{
MessageBox.Show(ae.GetBaseException().ToString());
}
原文链接:第十七节:从状态机的角度 async 和 await 的实现原理(新) - Yaopengfei - 博客园
C++后台开发
C/C++后台开发技术交流qun:720209036 2022-05-06 加入
还未添加个人简介
评论