Async Programming - 1 async-await 糖的本质(2)
上一篇讲了这么多,其实说的就是一个事,return会被编译器重写成SetResult,所以如果我们的异步函数返回的是一个Task<int>,代码就要改成这样:
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks; namespace StateMachineDemo
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(FooAsync2().Result);
} public static async Task<int> FooAsync()
{
await Task.Delay(10000);
return 42;
} public static Task<int> FooAsync2()
{
var stateMachine = new FooAsyncStateMachine();
stateMachine.MethodBuilder = new AsyncTaskMethodBuilder<int>(); stateMachine.MethodBuilder.Start(ref stateMachine); return stateMachine.MethodBuilder.Task;
} public struct FooAsyncStateMachine : IAsyncStateMachine
{
public AsyncTaskMethodBuilder<int> MethodBuilder;
public void MoveNext()
{
MethodBuilder.SetResult(42);
} public void SetStateMachine(IAsyncStateMachine stateMachine)
{
throw new NotImplementedException();
}
}
}
}
函数的返回值换成了Task<int>,里面返回MethodBuilder.Task,原来的AsyncVoidMethodBuilder对应换成了AsyncTaskMethodBuilder<int>,SetResult这里面return了一个值,就是这样。
下面来加点料,前面说的都是平凡的异步方法,现在我们真正的来在方法里面await一个东西,比如await Task.Delay(10000);
public static async Task<int> FooAsync()
{
await Task.Delay(10000);
return 42;
}
这个时候状态机就要真正的起作用了,这个代码有两个状态——await执行之前到开始执行、await执行之后,也就是说对于一个有n个await函数,他就有n+1个状态,分别是从开始到第1个await,第i到i+1个await,第n个await到结束。
对于我们的代码,不妨用一个int来表示状态,0是第一个状态,1是第二个状态,我们先在MoveNext里面把框架写好:
public struct FooAsyncStateMachine : IAsyncStateMachine
{
public AsyncTaskMethodBuilder<int> MethodBuilder; public int State; public void MoveNext()
{
if (State == 0)
{
//await之前以及开始await
return;
} if (State == 1)
{
//await之后
MethodBuilder.SetResult(42);
return;
}
} public void SetStateMachine(IAsyncStateMachine stateMachine)
{
throw new NotImplementedException();
}
}
好了现在看State==0的时候,这里面await之前没有代码,所以我们只需要启动Task.Delay(10000),首先是Task.Delay(10000);表达式的求值:
public void MoveNext()
{
if (State == 0)
{
Task.Delay(10000);
return;
} if (State == 1)
{
//await之后
MethodBuilder.SetResult(42);
return;
}
}
这样还不行,我们没法确定Task.Delay什么时候完成,编译器在这里是这样实现的:Task.Delay(10000);返回的是一个Task,对他调用GetAwaiter,拿到awaiter:
public void MoveNext()
{
if (State == 0)
{
TaskAwaiter awaiter = Task.Delay(10000).GetAwaiter();
//注册回调什么的
return;
} if (State == 1)
{
//await之后
MethodBuilder.SetResult(42);
return;
}
}
如果我们直接这么写
if (State == 0)
{
TaskAwaiter awaiter = Task.Delay(10000).GetAwaiter();
awaiter.GetResult();
return;
}
GetResult是个阻塞调用,卡住控制流就不好了,所以GetResult要在Task确定完成的时候,也就是State==1的时候(State被谁设为1我们之后讲),也就是在第二个if里面,GetResult
if (State == 1)
{
//await之后
awaiter.GetResult();
MethodBuilder.SetResult(42);
return;
}
然而你会发现TaskAwaiter的作用域是前一个if块,在这里我们访问不到,所以TaskAwaiter也应该作为类成员而不是方法内的局部变量,顺便说这东西也是一个struct,同样是我们不希望有额外的堆操作开销,最后改好的StateMachine是这样
public struct FooAsyncStateMachine : IAsyncStateMachine
{
public AsyncTaskMethodBuilder<int> MethodBuilder; public int State; private TaskAwaiter awaiter; public void MoveNext()
{
if (State == 0)
{
awaiter = Task.Delay(10000).GetAwaiter();
return;
} if (State == 1)
{
//await之后
awaiter.GetResult();
MethodBuilder.SetResult(42);
return;
}
} public void SetStateMachine(IAsyncStateMachine stateMachine)
{
throw new NotImplementedException();
}
}
还没完,第一个if里面是已经让awaiter跑起来了,还需要一个机制让它在完成的时候State变成1,以及让他调用MoveNext,走第二个if,继续后面的控制流。这里有一个简单的优化,我们先检测task有没有完成,如果完成了,那么把State设为1,直接goto到第二个if,如果没有的话,再注册让他完成时调用MoveNext的相关东西:
if (State == 0)
{
awaiter = Task.Delay(10000).GetAwaiter();
if (awaiter.IsCompleted)
{
State = 1;
goto state1;
}
else
{
//task还没完
}
return;
}
state1:
if (State == 1)
{
//await之后
awaiter.GetResult();
MethodBuilder.SetResult(42);
return;
}
}
如果task还没完,我们同样需要把State设为1,这样当然完了的时候,一个什么东西继续调用MoveNext,就能直接执行第二个if块的内容了。关于注册完成时调用MoveNext,这里有个API函数叫做
if (State == 0)
{
awaiter = Task.Delay(10000).GetAwaiter();
if (awaiter.IsCompleted)
{
State = 1;
goto state1;
}
else
{
State = 1;
MethodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
}
return;
}
StateMachine.AwaitUnsafeOnCompleted,还有一个函数叫AwaitOnCompleted,具体里面有什么差别我也不是很懂,总之编译器重写的时候用的是这个,两个参数,一个是awaiter,要等待完成的东西,一个this,完成之后调用this的MoveNext,逻辑很清楚,可以看到这里用了ref,为了把参数都转移到堆上,因为之后方法就返回栈就回退了,栈上的东西都会销毁。
看似什么都已经好了(其实并没有),我们运行一下看看:
哎,挂在了这里,这里我们应该写成这样:
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
MethodBuilder.SetStateMachine(stateMachine);
}
为什么一个method builder要SetStateMachine呢,回去看我们当初写的代码:
public static Task<int> FooAsync2()
{
var stateMachine = new FooAsyncStateMachine();
stateMachine.MethodBuilder = new AsyncTaskMethodBuilder<int>(); stateMachine.MethodBuilder.Start(ref stateMachine); return stateMachine.MethodBuilder.Task;
}
这里MethodBuilder是获得了一个ref,引用到到栈上的stateMachine,C#的ref你可以理解为指针:最开始,MethodBuilder里面有一个指向栈上stateMachine的指针,这很好,但是前面一步我们用MethodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref this); 调用把state machine本体转移到了堆上,所以原来MethodBuilder里面指向栈上StateMachine的引用(指针)就无效了,所以我们需要给MethodBuilder一个新的堆上的stateMachine的引用,让他持有正确的状态机。
现在都好了,我在if(State==1)那里下个断点,我们跑一下代码,看看回调是怎么发生的:
代码停在了这里,看调用堆栈,是另一条独立的线程继续调用了MoveNext,从Delay产生作用的TimerCallback一直向上,最后调用了MoveNext,值得一提的是,主线程被某种同步机制卡住了,因为那时候FooAsync2的结果还没有return出来给它打印,看看并行堆栈监视
的确是一条独立的线程调用了MoveNext,主线程也被卡住了。
总结一下,编译器会把第i个await重写为两个部分,一个是在状态i里面的GetAwaiter、测试IsCompleted,有的话直接跳第i+1个状态、没有的话注册回调,另一个是在状态i+1里面的GetResult。
就这么多,后面复杂的东西,比如AwaitUnsafeOnComplete为什么Unsafe,以及主线程被卡住的机制,我还不怎么懂,这篇主要是讲一下编译器怎么重写最简单的await,大概思路是这样,至于更复杂的await,比如if块里的,try-catch里的,不是很懂也就不说了。
Async Programming - 1 async-await 糖的本质(2)的更多相关文章
- Async Programming - 1 async-await 糖的本质(1)
这一个系列的文章主要来讲 C# 中的语言特性 async-await 在语言层面的本质,我们都知道 await 是编译器进行了一个 rewrite,然而这个 rewrite 并不是直接 rewrite ...
- C#的多线程——使用async和await来完成异步编程(Asynchronous Programming with async and await)
https://msdn.microsoft.com/zh-cn/library/mt674882.aspx 侵删 更新于:2015年6月20日 欲获得最新的Visual Studio 2017 RC ...
- Asynchronous programming with async and await (C#)
Asynchronous Programming with async and await (C#) | Microsoft Docs https://docs.microsoft.com/en-us ...
- Async Programming All in One
Async Programming All in One Async & Await Frontend (async () => { const url = "https:// ...
- [转] Scala Async 库 (Scala future, await, async)
[From] https://colobu.com/2016/02/15/Scala-Async/ 在我以前的文章中,我介绍了Scala Future and Promise.Future代表一个异步 ...
- async await 的 实质 本质
async await 的 实质 就是 用 “状态机” 来 取代 函数层层调用 . async await 的 本质 是 语法糖, 和 提高性能 什么的 没什么关系 . 为了避免理解歧义, 我把 ...
- Javascript 使用 async 声明符和 await 操作符进行异步操作
async function 声明用于定义一个返回 AsyncFunction 对象的异步函数 await 操作符用于等待一个Promise 对象.它只能在异步函数 async function 中 ...
- a kind of async programming in c#, need to reference definition
void Main() { Run d=new Run(RunHandler); IAsyncResult result= d.BeginInvoke(new AsyncCallback(CallBa ...
- [Javascript] Await a JavaScript Promise in an async Function with the await Operator
The await operator is used to wait for a promise to settle. It pauses the execution of an async func ...
随机推荐
- js参数传递分析
需要明白,js基本类型存放在栈,对象存放在堆. 结论:基本类型变量作为参数,不会改变变量值.对象变量作为参数,不修改属性(访问原始对象的操作),也不会改变变量值 起因,是群里一个问题: var a = ...
- HDU 5289
http://acm.hdu.edu.cn/showproblem.php?pid=5289 给一个数列,求有多少区间,使得这些区间内的最大值减最小值小于k 单调队列的功能:O(1) 插入,删除,最大 ...
- 用户 NT AUTHORITY\NETWORK SERVICE 登录失败 解决方法(转载)
用户 NT AUTHORITY\NETWORK SERVICE 登录失败 解决方法 (MS SQL 2005) Windows server 2003,2008 Web.Config 配置连接sql ...
- jquery检查元素存在性
javascript检查元素存在性: 即使这个元素被删除了,也不担心javascript代码报错: jquery检查元素存在性: 代码如下: if(!document.getElementById(& ...
- css技巧收集
1. 使用 :not() 为导航添加/取消边框 传统的方法为导航栏添加边框: /* add border */ .nav li { border-right: 1px solid #666; } /* ...
- 《JavaScript_DOM编程艺术第二版(中文)》整书笔记
目录 第3章:DOM 第4章:案例研究 第5章:最佳实践 第6章:案例改进 第7章:动态创建标记 第8章:充实文档的内容 第9章:CSS-DOM 第3章:DOM 文档:DOM中的"D&quo ...
- Flume NG安装部署及数据采集测试
转载请注明出处:http://www.cnblogs.com/xiaodf/ Flume作为日志收集工具,监控一个文件目录或者一个文件,当有新数据加入时,采集新数据发送给消息队列等. 1 安装部署Fl ...
- NotePad++ 调试PHP代码中文显示乱码
最近在NotePad++上调试PHP代码,按照示例代码进行调试,结果在显示中文的时候显示一堆乱码,于是上网百度,有2种方法可以解决: 按调试方式有2种方法: 1.菜单插件-NppExec: " ...
- 吴奇隆刘诗诗婚礼场地:巴厘岛Ayana酒店,美到窒息!
导读:忍不住转载一下,原文地址:http://www.sjq315.com/news/270768.html 3月20日,吴奇隆和刘诗诗在巴厘岛五星级酒店Ayana Resort and Spa酒店举 ...
- sql server 表中的编号自增
http://jingyan.baidu.com/article/fec4bce244f902f2608d8b7a.html