await修饰的方法返回的是一个Task,而这个Task其实就是一个异步句柄,如果我来取名字的话多半就叫做IAsyncHandler。
一个IAsyncHandler你可以想象成是这么一个东西:
public interface IAsyncHandler { Register( Action continuation ); }
这是伪代码,事实上不存在这么个东西。
注册一个回调方法在异步操作完成后继续,所以事实上这段代码的原理像是这样的:
private async void btnDoStuff_Click(object sender, RoutedEventArgs e) { btnDoStuff.IsEnabled = false; lblStatus.Content = "Doing Stuff"; var handler = Task.Delay(4000) as IAsyncHandler handler.Register( () => { lblStatus.Content = "Not Doing Anything"; btnDoStuff.IsEnabled = true; } ); }
当然上面全是伪代码,但是如果你能看懂这段代码在干什么,那么async基本就可以懂了,剩下的只是一些实现细节上的问题。
通常情况下,Task.Delay会立即返回一个Task对象,这个Task对象会在指定时间之后被标记为Completed,而被标记Completed就会立即开一个线程来进行延续的操作。
但是这里有个问题就是你这个方法是写在UI线程里面的,控件的事件会被UI线程触发,而UI线程上有个SynchronizationContext对象,这个对象的存在就会使得系统在异步回调的时候去捕获源线程。在原来的线程(UI线程)去执行延续的任务。
而我们知道WinForm里面有个方法叫做Control.Invoke,可以把一个方法封送到UI线程去执行,而上面的工作和这个方法底层的原理其实是一样的,所以,其实这段代码用传统的思维来理解的话像是这样:
private async void btnDoStuff_Click(object sender, RoutedEventArgs e) { btnDoStuff.IsEnabled = false; lblStatus.Content = "Doing Stuff"; Action continuation = () => { lblStatus.Content = "Not Doing Anything"; btnDoStuff.IsEnabled = true; }; Thread.Start( () => { Thread.Sleep( 4000 ); Control.Invoke( continuation ); } ); }