无论是在服务器端,还是在客户端,异步编程都一直大行其道。在服务器端,为提高性能,支撑更多的并发事务,线程不能够被阻塞;在客户端,为能总是及时地响应用户的操作, 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) |
03 |
Console.WriteLine( "大量的计算,放入工作线程中进行:" ); |
06 |
int ret = await Task< int >.Run(() => { |
08 |
for ( int i = 0; i < 1000; i++) |
16 |
Console.WriteLine( "得到结果:{0}" ,ret); |
再如我们要在控件的事件响应中延时几秒钟,可以这样做:
1 |
private async void ok_Click( object sender, EventArgs e) |
3 |
Console.WriteLine( "异步延时,不会阻塞UI线程" ); |
5 |
await Task.Delay(5000); |
7 |
Console.WriteLine( "延时时间到,会走到这里!" ); |
你也很容易想到与上面等价的解决办法:
1 |
private async void ok_Click( object sender, EventArgs e) |
3 |
Console.WriteLine( "异步延时,不会阻塞UI线程" ); |
5 |
await Task.Run( ()=>Thread.Sleep(5000) ); |
7 |
Console.WriteLine( "延时时间到,会走到这里!" ); |
这里需要提醒下,Task.Run模式只是一种简单的包装,需要在新的线程中执行异步代码,因此并不是一种高效的模式。下面我们自行构造一个“异步”函数,实现同样的延时效果而无需创建新线程(这样才能真正提升性能而不仅仅是提升可响应性,然而这是另外一个故事了)。这通常要借助TaskCompletionSource对象:
01 |
private async void ok_Click( object sender, EventArgs e) |
03 |
Console.WriteLine( "异步延时,不会阻塞UI线程" ); |
07 |
Console.WriteLine( "延时时间到,会走到这里!" ); |
10 |
public Task MyDelay( int ms) |
12 |
TaskCompletionSource< bool > tcs = null ; |
14 |
System.Threading.Timer timer = new System.Threading.Timer( |
16 |
tcs.TrySetResult( true ); |
21 |
tcs = new TaskCompletionSource< bool >(timer); |
TaskCompletionSource常常用来构造Task对象,当异步操作要结束时设置其结果(TrySetResult)即可。然而,并非只有Task对象才是可await的,实际上,只要实现了GetAwaiter()方法(这个方法返回的对象要实现INotifyCompletion接口)的对象,均是awaitable的。
那么,int(Int32)类型可不可以被await?当然可以,只要int类型具有GetAwaiter()方法即可!
我们先实现可完成异步延时、实现INotifyCompletion接口的DelayAwait类:
01 |
public class DelayAwait : INotifyCompletion |
04 |
private bool finished; |
06 |
public DelayAwait( int ms) |
11 |
public bool IsCompleted |
13 |
get { return finished; } |
16 |
public void OnCompleted(Action continuation) |
29 |
public void GetResult() { } |
然后,我们让int类型"具有" GetAwaiter()方法,这很简单,利用扩展方法特性即可:
1 |
public static DelayAwait GetAwaiter( this int ms) |
3 |
return new DelayAwait(ms); |
ok,这样我们以后就可以这样完成异步延时的功能了:
1 |
private async void ok_Click( object sender, EventArgs e) |
3 |
Console.WriteLine( "异步延时,不会阻塞UI线程" ); |
7 |
Console.WriteLine( "延时时间到,会走到这里!" ); |
我们需要注意DelayAwait类的实现,INotifyCompletion只规定了必须实现OnCompleted方法,但实际上要支持await还必须提供IsCompleted属性和GetResult方法。c#编译器遇到await的代码,会在背后偷偷地、自动生成一个状态机来真正实现“异步”,我们不要以为方法真的会被“中断”,这不过都是编译器施展的魔法。在编译器自动生成的代码中,会调用 IsCompleted、 OnCompleted、 GetResult这些方法,因此我们也必须实现这些方法。简单地说,会先判断 IsCompleted,若为假,则会调用 OnCompleted方法,在这个方法中我们注册当异步操作完成时需要做的后续操作——也即await后的代码。如果大家想弄清楚这背后的机制,推荐阅读 深入探究 WinRT 和 await 。
这样看来,一切皆可awaitable。
附(参考资料):
Async/Await FAQ