在C# 5.0中引入了await关键字,通过它可以非常容易的实现异步操作。在大多数的时候,await一般适合Task一起使用的,也非常方便。但有的时候,我们也需要自定义我们自己的awaitable类型,以实现更高的灵活性和效率。
可以用于await运算符的对象要求如下:
- 有一个GetAwaiter()方法或扩展方法,它返回一个实现了 INotifyCompletion 接口的awaiter对象(或结构)
- 返回的awaiter对象(或结构)要求实现如下方法:
- void OnCompleted(Action continuation)
- bool IsCompleted { get ; }
- TResult GetResult() // TResult 也可以是 void 类型
下面我就简单的介绍一下await运算符是如何实现异步操作的。
例如,对于如下代码
var j = await 3; DoContinue(j);
在编译的时候会被编译器翻译为类似如下流程的代码(注:这个只是功能类似的简化流程示例,实际并非如此)。
var awaiter = 3.GetAwaiter(); var continuation = new Action(() => { var j = awaiter.GetResult(); DoContinue(j); }); if (awaiter.IsCompleted) continuation(); else awaiter.OnCompleted(continuation);
有了这个基础,我们就可以对一个int型的变量实现await操作了:
class Program { static void Main(string[] args) { Test(); Console.ReadLine(); } async static void Test() { var j = await 3; Console.WriteLine(j); } } class MyAwaiter : System.Runtime.CompilerServices.INotifyCompletion { public bool IsCompleted { get { return false; } } public void OnCompleted(Action continuation) { Console.WriteLine("OnCompleted"); ThreadPool.QueueUserWorkItem(_ => { Thread.Sleep(1000); this.result = 300; continuation(); }); } int result; public int GetResult() { Console.WriteLine("GetResult"); return result; } } static class Extend { public static MyAwaiter GetAwaiter(this int i) { return new MyAwaiter(); } }
这样我们就能看出await是如何实现call/cc式的异步操作了:
- 编译器把后续操作封装为一个Action对象 continuation ,传入 awaiter的 OnCompleted 函数并执行。
- awaiter在 OnCompleted 函数中执行异步操作,并在异步操作完成后(一般是异步调用的回调函数)执行 continuation 操作。
- continuation 操作的第一步就是调用awaiter.GetResult()获取异步操作的返回值,并继续执行后续操作。
看到这里,相信大家对await的机制已经有了简单的认识,也就不难理解为什么AsyncTargetingPack能使得.net 4.0程序也支持await操作了——该库在AsyncCompatLibExtensions类中对Task类提供了GetAwaiter扩展函数而已。