重新认识 async/await 语法糖
提起.net中的 async/await,相信很多.neter 第一反应都会是异步编程,其本质是语法糖,但继续追查下去,既然是语法糖,那么经过编译之后,真正的代码是什么样的,如何执行的?带着这些疑问,通过网上资料的查询,可以了解到编译之后,是通过实现 iasyncstatemachine 的一个状态机来实现的,博客园里大神jeffcky 已经说得很清楚了,传送门: https://www.cnblogs.com/createmyself/p/5983208.html
上述知识对我们理解 async/await 非常重要,但不是本文讨论的侧重点,触发笔者写这篇文章的初衷是:
1.只有task可以被await吗,await之后就一定是异步执行吗?
答案当然不是,google了一圈后发现,当一个类可以被await,必须满足以下条件:
a.它必须包含 getawaiter() 方法(实例方法或者扩展方法) // 手动划重点:扩展方法,聪明的你是不是立马有些思想火花
b.getawaiter() 返回awatier实例,并且这个实例包含如下条件:
- 必须实现 inotifycompletion 或者 icriticalnotifycompletion 接口
- 必须包含 iscompleted 公共属性
- 必须包含 getresult() 方法,返回void或者其他返回值
上述条件中inotifycompletion 接口信息如下:
// // 摘要: // represents an operation that schedules continuations when it completes. public interface inotifycompletion { // // 摘要: // schedules the continuation action that's invoked when the instance completes. // // 参数: // continuation: // the action to invoke when the operation completes. // // 异常: // t:system.argumentnullexception: // the continuation argument is null (nothing in visual basic). void oncompleted(action continuation); }
重点上述对于参数 continuation 的解释:委托在操作完成之后调用。此处遗留一个问题:在谁的操作完成之后调用,是怎么调用的?
先把上述问题放一边,我们来自己写一个可以被await的类,并且观察前后执行的顺序以及是否存在线程切换:
public class program { static async task main (string[] args) { console.writeline ($"begin awati,thread id is {thread.currentthread.managedthreadid}"); int result = await new customawaitable (); console.writeline ($"end await,result is {result},thread id is {thread.currentthread.managedthreadid}"); await task.delay (timeout.infinite); } } public class customawaitable : inotifycompletion { public void oncompleted (action continuation) { console.writeline ($"invoke continuation action on completed,thread id is {thread.currentthread.managedthreadid}"); continuation?.invoke (); } public int getresult () { console.writeline ($"get result,thread id is {thread.currentthread.managedthreadid}"); return 100; } public bool iscompleted { get; set; } public customawaitable getawaiter(){ return this; } }
上述代码中,customawaitable 实例满足了可被await的所有条件,并且正常通过编译,运行后发现结果如下:
ps d:\git\awaitable\src> dotnet run begin main,thread id is 1 get awatier,thread id is 1 begin invoke continuation action on completed,thread id is 1 get result,thread id is 1 end main,result is 100,thread id is 1 end invoke
根据上述日志,可以看出:
- 执行前后线程并未发生切换,所以当我们不假思索的回答 await/async 就是异步编程时,至少是一个不太严谨的答案
- 最后执行日志 "end invoke" 表明:continuation action 这个委托,根据上述调用日志顺序可以大致理解为:编译器将await之后的代码封装为这个 action,在实例完成后调用oncompleted方法执行了await 之后的代码(注:实际情况比较复杂,如果有多行await,会转换为一个状态机,具体参看文章开头给出的连接)。
2.了解了上述知识之后,那么我们常规所说的await task异步编程又是怎么回事呢?
- 先来看task部分源码(传送门):
上述红框代码显示,task在getawaiter中创建了 taskawaiter对象,并将this传递。
- 再来看taskawaiter源码(传送门):
看到此处,有了前面的知识,我们会对await task有了更加深入的理解:
task通过增加一个getawatier()函数,同时将自身传递给taskawaiter类来实现了await语法糖的支持,同时在执行时,调用getresult()函数的本质是通过 task.wait等待异步线程的执行完成,然后通过回调进行后续的操作。
总结
本文主要对 async/await 语法糖进行分析验证,同时通过对task源码分析,更加深入的理解此语法糖本身的语法,相信通过通过此文,对大家从多个角度去理解异步编程有帮助,我自己也在不停的学习。
本文代码示例地址:https://github.com/xboo/awaitable
推荐阅读
-
详解小程序原生使用ES7 async/await语法
-
python3.6以上 asyncio模块的异步编程模型 async await语法
-
重新认识 async/await 语法糖
-
C#语法——await与async的正确打开方式
-
【前端】vue前端交互模式-es7的语法结构?async/await
-
前端Tips#6 - 在 async iterator 上使用 for-await-of 语法糖
-
js 异步之ES7函数async函数Generator 函数的语法糖
-
generator及其语法糖async/await
-
async/await是Promise的语法糖
-
js异步编程三、生成器函数、Gennerator、Async/Await语法糖