构造awaitable对象

无论是在服务器端,还是在客户端,异步编程都一直大行其道。在服务器端,为提高性能,支撑更多的并发事务,线程不能够被阻塞;在客户端,为能总是及时地响应用户的操作, UI线程也不能阻塞,这都是需要异步编程大显神通的场合。但异步编程是困难的,串行的业务逻辑被打散,零落在代码的各个角落之中。

    .net一直试图降低异步编程的难度,从最开始的begin/end-invoke模式(APM),到基于事件的异步模式(EAP),这些技术仍未解决逻辑流程被打乱的问题,直到出现了async和await。在最新发布的win8和wp8中,微软都强调应用的可响应性,因此async和await的出现正逢其时。具体这两个关键字如何使用,大家可查阅MSDN等资料,在这里我想说明的是如何构造可异步调用(awaitable)的对象。当然,Task和Task<Result>是最常见的可await的对象,如:

 

01 private async void ok_Click(object sender, EventArgs e)
02 {
03      Console.WriteLine("大量的计算,放入工作线程中进行:");
04  
05      //异步操作
06      int ret = await Task<int>.Run(() => {
07                 int total = 0;
08                 for (int i = 0; i < 1000; i++)
09                 {
10                     total += i;
11                 }
12  
13                 return total;
14      });
15  
16      Console.WriteLine("得到结果:{0}",ret);
17  
18 }

 

    再如我们要在控件的事件响应中延时几秒钟,可以这样做:

1 private async void ok_Click(object sender, EventArgs e)
2 {
3      Console.WriteLine("异步延时,不会阻塞UI线程");
4  
5      await Task.Delay(5000);
6  
7      Console.WriteLine("延时时间到,会走到这里!");
8 }

 

    你也很容易想到与上面等价的解决办法:

 

1 private async void ok_Click(object sender, EventArgs e)
2 {
3      Console.WriteLine("异步延时,不会阻塞UI线程");
4  
5      await Task.Run( ()=>Thread.Sleep(5000) );
6  
7      Console.WriteLine("延时时间到,会走到这里!");
8 }

 

    这里需要提醒下,Task.Run模式只是一种简单的包装,需要在新的线程中执行异步代码,因此并不是一种高效的模式。下面我们自行构造一个“异步”函数,实现同样的延时效果而无需创建新线程(这样才能真正提升性能而不仅仅是提升可响应性,然而这是另外一个故事了)。这通常要借助TaskCompletionSource对象:

01 private async void ok_Click(object sender, EventArgs e)
02 {
03     Console.WriteLine("异步延时,不会阻塞UI线程");
04  
05     await MyDelay(5000);
06  
07     Console.WriteLine("延时时间到,会走到这里!");
08 }
09  
10 public Task MyDelay(int ms)
11 {
12     TaskCompletionSource<bool> tcs = null;
13  
14     System.Threading.Timer timer = new System.Threading.Timer(
15              (obj) => { //超时时间到来后的回调
16                          tcs.TrySetResult(true);
17                       },
18                    tcs,-1,-1
19            );
20  
21      tcs = new TaskCompletionSource<bool>(timer);
22      timer.Change(ms, -1);
23  
24      return tcs.Task;
25 }

 

       TaskCompletionSource常常用来构造Task对象,当异步操作要结束时设置其结果(TrySetResult)即可。然而,并非只有Task对象才是可await的,实际上,只要实现了GetAwaiter()方法(这个方法返回的对象要实现INotifyCompletion接口)的对象,均是awaitable的。

 

       那么,int(Int32)类型可不可以被await?当然可以,只要int类型具有GetAwaiter()方法即可!

 

       我们先实现可完成异步延时、实现INotifyCompletion接口的DelayAwait类

 

01 public class DelayAwait : INotifyCompletion
02 {
03         private int delay;
04         private bool finished;
05  
06         public DelayAwait(int ms)
07         {
08             this.delay = ms;
09         }
10  
11         public bool IsCompleted
12         {
13             get { returnfinished; }
14         }
15  
16         public void OnCompleted(Action continuation)
17         {
18             new Timer(
19                    (obj) => {
20                        continuation();  //定时完成,调用await之后的代码
21                        finished = true;
22                       }, 
23                    this,
24                    delay, //延时时长
25                    -1               
26                 );
27         }
28  
29         public void GetResult() { }
30 }

     然后,我们让int类型"具有" GetAwaiter()方法,这很简单,利用扩展方法特性即可:

 

1 public static DelayAwait GetAwaiter(this int ms)
2 {
3     return new DelayAwait(ms);
4 }

     ok,这样我们以后就可以这样完成异步延时的功能了:

 

1 private async void ok_Click(object sender, EventArgs e)
2 {
3     Console.WriteLine("异步延时,不会阻塞UI线程");
4  
5     await 5000;  //因为int类型扩展实现了GetAwaiter(),因此是awaitable的
6  
7     Console.WriteLine("延时时间到,会走到这里!");
8 }

     我们需要注意DelayAwait类的实现,INotifyCompletion只规定了必须实现OnCompleted方法,但实际上要支持await还必须提供IsCompleted属性和GetResult方法。c#编译器遇到await的代码,会在背后偷偷地、自动生成一个状态机来真正实现“异步”,我们不要以为方法真的会被“中断”,这不过都是编译器施展的魔法。在编译器自动生成的代码中,会调用 IsCompleted、 OnCompleted、 GetResult这些方法,因此我们也必须实现这些方法。简单地说,会先判断 IsCompleted,若为假,则会调用 OnCompleted方法,在这个方法中我们注册当异步操作完成时需要做的后续操作——也即await后的代码。如果大家想弄清楚这背后的机制,推荐阅读  深入探究 WinRT 和 await 。

 

 

    这样看来,一切皆可awaitable。

 

 

 

    附(参考资料):

 

     Async/Await FAQ

 

posted on 2012-12-15 15:36  南方青年  阅读(880)  评论(1编辑  收藏  举报

导航