.NET 基于Task的异步编程模型

来源:互联网 发布:邮箱daum数据 编辑:程序博客网 时间:2024/05/16 03:42

最近下载了Visual Studio Async CTP,体验了下基于Task的异步编程带来的新特性。在CTP中,增加了新的关键字: async, await。尤其是在SL,WP7的编程中,大量使用异步调用的环境里,async, await的确能减少编程的复杂度。看上去像是同步的方法,其实编译器做了些手脚,悄悄的生成了回调的代码。比如:

view plain
  1. private async void button_Click(object sender, EventArgs e)  
  2. {  
  3.     var client = new WebClient();  
  4.     var result = await client.DownloadStringTaskAsync("http://www.csdn.net");  
  5.     textBox1.Text = result;  
  6.     MessageBox.Show("Complete");  
  7. }  
上面的代码,await 这一行的 DownloadStringTaskAsync (CTP中AsyncCtpLibrary.dll中提供的WebClient的扩展方法) 是异步执行的,之后两行被包装成回调。
(另外,注意:在button_Click的void之前加上了async关键字)


按照原来的写法:
view plain
  1. private void button3_Click(object sender, EventArgs e)  
  2. {  
  3.     var client = new WebClient();  
  4.     client.DownloadStringCompleted += (s, evt) => {  
  5.         textBox1.Text = evt.Result;  
  6.         MessageBox.Show("Complete");  
  7.     };  
  8.     client.DownloadStringAsync(new Uri("http://www.csdn.net"));  
  9. }  
比较代码可以感觉到 async,await 的出现,一下把我们从“先定义回调”的思想变回“同步调用"的顺序时代。

先回顾一下目前为止我们使用的异步编程方法:

1. 最简单的Thread:

view plain
  1. var thread = new Thread((obj) =>  
  2. {  
  3.     // 模拟复杂的处理  
  4.     Thread.Sleep(1000);  
  5.     Console.WriteLine(obj);  
  6. });  
  7. thread.Start("some work");  
这种处理方式,麻烦在于处理返回值上,通常还要设计个包装类封装个Result 属性以获得返回值。

2. Thread的包装演变出 APM  (Asynchronous Programming Model):
最典型的代表:Delegate.BeginInvoke / EndInvoke。WCF的客户端代理类如果选择生成异步方法,那么也是BeginXXX,EndXXX这样的方法。

view plain
  1. Func<stringstring> func = x =>  
  2. {  
  3.     // 模拟复杂的处理  
  4.     Thread.Sleep(1000);  
  5.     return x + " is completed";  
  6. };  
  7. func.BeginInvoke("some work", ir =>  
  8. {  
  9.     AsyncResult ar = (AsyncResult)ir;  
  10.     var delegateInstance = (Func<stringstring>)ar.AsyncDelegate;  
  11.     var result = delegateInstance.EndInvoke(ir);  
  12.     Console.WriteLine(result);  
  13. }, null);  

3. 基于事件的APM——EAP(Event-based Asynchronous Pattern)
上面的BeginXXX,EndXXX的异步编程模型,在Callback上还是略显笨重,因此又演变出基于事件注册回调方法的模式。
最典型的代表就是 WebClient (HttpWebRequest 等)
view plain
  1. var client = new WebClient();  
  2. client.DownloadStringCompleted += (s, evt) => {  
  3.     textBox1.Text = evt.Result;  
  4.     MessageBox.Show("Complete");  
  5. };  
  6. client.DownloadStringAsync(new Uri("http://www.csdn.net"));  

4. 基于Task的APM——TAP(Task-based Asynchronous Pattern)
.net 4.0 里引入了并行编程库,Task成为新的异步编程主角,async, await 语法糖应运而生。为了实现async,await编译器将每个被async关键字标记的方法编译为一个方法所在类的一个内嵌类,所有在方法体内出现的变量会被转为这个类的字段,如果是一个实例方法,那么this所代表的对象也被声明为一个字段。这个类有两个核心成员:一个int来保存代码执行到哪一步的state,一个方法来执行真正的动作的 MoveNext() 方法。

比如下面的方法:

view plain
  1. static async void SimpleAsyncTest()  
  2. {   
  3.     var client = new WebClient();  
  4.     var result1 = await client.DownloadStringTaskAsync("http://www.csdn.net");  
  5.     Console.WriteLine("complete");  
  6. }  
被转化为如下:注:Reflector反射出来的名字都是特殊字符,下面的类名变量名为了可读已做替换。
view plain
  1. private static void SimpleAsyncTest()  
  2. {  
  3.     SimpleAsyncTestObj obj = new SimpleAsyncTestObj(0);  
  4.     obj.MoveNextDelegate = new Action(obj, (IntPtr)this.MoveNext);  
  5.     obj.builder = AsyncVoidMethodBuilder.Create();  
  6.     obj.MoveNext();  
  7. }  

SimpleAsyncTestObj:

view plain
  1. [CompilerGenerated]  
  2. private sealed class SimpleAsyncTestObj  
  3. {  
  4.     // Fields  
  5.     private bool disposing;  
  6.     public AsyncVoidMethodBuilder builder;  
  7.     private int state;  
  8.     public Action MoveNextDelegate;  
  9.     private TaskAwaiter<string> awaiter;  
  10.     public WebClient client;  
  11.     public string result;  
  12.   
  13.     // Methods  
  14.     [DebuggerHidden]  
  15.     public SimpleAsyncTestObj(int state);  
  16.     [DebuggerHidden]  
  17.     public void Dispose();  
  18.     public void MoveNext();  
  19. }  

可以看到async方法被转化成类,然后调用了 MoveNext() ,初始的 state == 0,第一次调用如果没有 IsCompleted 则注册 awaiter 的 OnCompleted 事件,绑定的还是 MoveNext 方法。等awaiter 回调时,状态==1,直接进入 Label_0086 部分的代码,用 GetResult() 获取结果。

view plain
  1. public void MoveNext()  
  2. {  
  3.         try  
  4.         {  
  5.             string str;  
  6.             bool flag = true;  
  7.             if (this.state != 1)  
  8.             {  
  9.                 if (this.state != -1)  
  10.                 {  
  11.                     this.client = new WebClient();  
  12.                     this.awaiter = this.client.DownloadStringTaskAsync("http://www.csdn.net").GetAwaiter<string>();  
  13.                     if (this.awaiter.IsCompleted)  
  14.                     {  
  15.                         goto Label_0086;  
  16.                     }  
  17.                     this.state = 1;  
  18.                     flag = false;  
  19.                     this.awaiter.OnCompleted(this.MoveNextDelegate);  
  20.                 }  
  21.                 return;  
  22.             }  
  23.             this.state = 0;  
  24.         Label_0086:  
  25.             str = this.awaiter.GetResult();  
  26.             this.awaiter = new TaskAwaiter<string>();  
  27.             this.result = str;  
  28.             Console.WriteLine("over");  
  29.         }  
  30.         catch (Exception exception)  
  31.         {  
  32.             this.state = -1;  
  33.             this.builder.SetException(exception);  
  34.             return;  
  35.         }  
  36.         this.state = -1;  
  37.         this.builder.SetResult();  
  38. }  

如果方法中有多个 await 关键字的话,编译器生成的 MoveNext 则会是下面的样子:
public void MoveNext()
{
    switch (state)
    {
        case 1:
            ...
            state++;
            // 注册第一个await对应异步的回调处理:回调中,获取结果,并调用下一个异步
            break;
        case 2:
            ...
            state++;
            // 注册第二个await对应异步的回调处理:回调中,获取结果,并调用下一个异步
            break;
        case 3:
            ....
            state++;
            break;
         。。。
    }
}
通过回调把下一个异步串起来,这么看来确实有 yield return 的感觉。看到这里相信大家都看出来在使用 TAP 编程时,最重要的是进行Task的设计,比如:

view plain
  1. static async void DoSomeWork(int i, string arg)  
  2. {  
  3.     await new TaskFactory().StartNew(() =>  
  4.     {  
  5.         var random = new Random();  
  6.         // 模拟复杂的处理  
  7.         Thread.Sleep(i * 1000);  
  8.         Console.WriteLine(arg + " end");  
  9.     });  
  10. }  
还可以利用 TaskFactory.ContinueWhenAll / ContinueWhenAny 等特性编排 Task。 以实现更加灵活的异步编程