实际上异步方法在GUI程序中尤为有用,GUI程序在设计上就要求所有的现实变化都必须在GUI主线程上完成,比如点击按钮,展示标签,移动窗体等。Windows程序通过消息来实现这一点,消息会被放入由消息泵管理的消息队列中。
消息泵从消息队列中取出一条信息,并调用它的处理程序(handler)代码。当处理程序代码完成时,消息泵获取下一条消息并循环这个过程。
因为这种机制,处理程序就必须短小精悍,才不至于挂起并阻碍其他GUI行为的处理。如果某个消息的处理程序代码耗时过长,消息队列中的消息就会产生挤压,程序会失去响应,因为在哪个长时间运行的处理程序完成之前,无法处理其他任何消息。
为了模拟一个长耗时的任务造成的影响,建立如下的WPF程序:
<Window x:Class="MessagePump_AsyncSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <StackPanel> <Label Name="lblStatus" Margin="10,5,10,0">Not Doing Anything</Label> <Button Name="btnDoStuff" Content="Do stuff" HorizontalAlignment="Left" Margin="10,5" Padding="5,2" Click="btnDoStuff_Click"></Button> </StackPanel> </Window>
public MainWindow() { InitializeComponent(); } private async void btnDoStuff_Click(object sender, RoutedEventArgs e) { btnDoStuff.IsEnabled = false; lblStatus.Content = "Doing Stuff"; Thread.Sleep(4000); //await Task.Delay(4000); lblStatus.Content = "Not Doing Anything"; btnDoStuff.IsEnabled = true; }
用户点击按钮后:禁用按钮——将标签文本修改为Doing Stuff,这样用户会知道程序在运行——休眠4s,模拟长耗时任务——将标签改为原始文本,并启用按钮。
但实际上,点击按钮后,什么也没有发生,4s内也不能移动窗体,4s后窗体才会出现在新位置。
原因在于,点击按钮哦是,click消息放入消息队列,消息泵从队列中移除该信息并开始处理点击按钮的处理程序代码,即btnDoStuff_Click方法中的代码,btnDoStuff_Click将希望发生的行为放入队列(禁用按钮,改变文本,移动窗体,改变文本,恢复按钮),但在处理程序本身退出之前,这些消息都无法执行(4s),然后所有的行为都发生了,但速度太快看不到。
但如果,处理程序能将前2条消息压入队列,然后将自己从处理器上摘下,4s后再压入队列,那么这些消息都可以在等待的时间内被处理,还能保持响应。
async,await就可以做到,当到达await关键字时,处理程序返回到调用方法,并从处理器上摘下,这时其他消息得到,包括最初的2条记录,在空闲任务(Task.Delay)完成后,后续部分又重新被安排到线程上。