内容简介:其实这篇文章的内容可以参考《CLR via C#》里的第28章,不过总觉得书上的还不够直观,所以干脆自己来整理一下好了。新建一个控制台项目,代码如下,三个非常简单的方法。现在用
其实这篇文章的内容可以参考《CLR via C#》里的第28章,不过总觉得书上的还不够直观,所以干脆自己来整理一下好了。
新建一个控制台项目,代码如下,三个非常简单的方法。
static async Task<string> MyMethodAsync(int argument) { var t1 = await GetType1(); var t2 = await GetType2(); return "Complete"; } static async Task<Type> GetType1() { await Task.Run(() => { Console.WriteLine("GetType1"); }); return typeof(string); } static async Task<Type> GetType2() { await Task.Run(() => { Console.WriteLine("GetType2"); }); return typeof(int); }
现在用 ILSpy
将其转换为 C# 4.0
的代码之后(因为 C# 4.0
没有 async
await
,所以能看到状态机),我们来看看状态机的原理:
代码经过可读性优化,并非原版。
/* 指出这是一个异步方法,并指出状态机的实现是哪个 class\struct */ [AsyncStateMachine(typeof(<MyMethodAsync>d__1))] [DebuggerStepThrough] private static Task<string> MyMethodAsync(int argument) { // 创建状态机实例并初始化 <MyMethodAsync>d__1 stateMachine = new <MyMethodAsync>d__1(); stateMachine.argument = argument; // 将方法实参拷贝到状态机的字段上 // 创建 builder ,从这个方法存根(我觉得理解为变量即可)上返回 Task<string> stateMachine.t__builder = AsyncTaskMethodBuilder<string>.Create(); stateMachine.m_state = -1; // 设置状态的初始位置 // 开始执行状态机 stateMachine.t__builder.Start(ref stateMachine); return stateMachine.t__builder.Task; // 返回状态机的 Task } // 这两个方法不重要,当摆设展示下结构即可 [AsyncStateMachine(typeof(<GetType1>d__2))] [DebuggerStepThrough] private static Task<Type> GetType1() [AsyncStateMachine(typeof(<GetType2>d__3))] [DebuggerStepThrough] private static Task<Type> GetType2() /* 这是状态机本身,Release 下是 struct 原因:https://stackoverflow.com/questions/23609110/why-are-awaiters-async-await-structs-and-not-classes-can-classes-be-used */ [CompilerGenerated] private sealed class <MyMethodAsync>d__1 : IAsyncStateMachine { /* 当前状态机的位置 */ public int m_state; /* 状态机的 builder */ public AsyncTaskMethodBuilder<string> t__builder; /* MyMethodAsync 方法的实参和局部变量此时会变成状态机的字段 所以一共有三个 */ public int argument; private Type m_t1; private Type m_t2; /* 每个 awaiter 类型一个字段,我这里 GetType1 跟 GetType2 都返回的是 Type 类型 所以这里只有一个字段 当你的方法中涉及到多个 Awaiter 类型时,就会有多个字段,但任何时候只有最近执行的、完成的那一个字段是重要的 */ private TaskAwaiter<Type> m_awaiterType1; /* 这是状态机方法 */ void IAsyncStateMachine.MoveNext() { int num = m_state; string result; // 结果值 try // 编译器插入 try 块保证状态机的任务完成 { TaskAwaiter<Type> awaiter1; TaskAwaiter<Type> awaiter2; switch (num) { /* 这里理解成case -1就行了,意为状态机第一次执行 也就是 var t1 = await GetType1(); */ default: awaiter1 = GetType1().GetAwaiter(); // 调用 GetType1 方法并拿到其 Awaiter if (!awaiter1.IsCompleted) // 这里做判断是避免多次执行 { num = (m_state = 0); // 等一会儿返回状态机时走到哪个位置去 m_awaiterType1 = awaiter1; // 保存 awaiter 以便将来返回 stateMachine = this; /* 告诉 awaiter 在 GetType1 的异步方法执行完毕后,调用 MoveNext 具体一点,这句代码会调用 awaiterType1 的 OnCompleted 它会在被等待的任务上调用 ContinueWith(t=> MoveNext()... 表示任务完成后调用 MoveNext 方法重返状态机 */ t__builder.AwaitUnsafeOnCompleted(ref awaiter1, ref stateMachine); /* 线程返回至调用者 */ return; } goto case unNamed; /* GetType1 方法异步完成了 */ case 0: awaiter1 = m_awaiterType1; // 恢复最新的 awaiter m_t1 = awaiter1.GetResult(); // 获取 GetType1 方法的结果 // 开始调用 GetType2 awaiter2 = GetType2().GetAwaiter(); if (!awaiter2.IsCompleted) { num = (m_state = 1); // 下一次 MoveNext 时走到 case 1 中 m_awaiterType1 = awaiter2; t__builder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine); return; } break; case 1: { awaiter2 = m_awaiterType1; // 恢复最新的 awaiter m_t2 = awaiter2.GetResult(); // 获取 GetType2 方法的结果 result = "Complete"; // 设置 MyMethodAsync 方法最终的返回结果 // 注意这里没有 break,还要继续执行 } } catch (Exception exception) { // 有异常:通过设置异常来完成状态机的 Task t__builder.SetException(exception); return; } // 无异常:通过返回结果来完成状态机的 Task t__builder.SetResult(result); } }
状态机总结
简单总结以下状态机的几个工作要点:
-
安排好
awaiter
之后就 返回原调用线程避免阻塞 -
在安排时
更新状态机恢复之后需要执行的位置(
m_state
) -
安排后在
awaiter
的任务上调用ContinueWith(t=> MoveNext())
来以便返回状态机
可以简单理解为你的 async
方法中有几个 await
,以上三步就要在状态机中重复几次。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。