GUI程序在设计上要求所有的显示变化都必须在主GUI线程中完成,Windows程序是通过消息来实现这一点的,消息被放入由消息泵管理的消息队列中。
消息泵从列队中取出一条消息,并调用它的处理程序代码。当程序代码完成时,消息泵获取下一条消息并循环这个过程。
由于这个架构,处理程序代码就必须矮小精悍,这样才不至于扶起并阻碍其他GUI行为处理。如果某个消息的处理程序代码耗时过长,消息队列中的消息会产生积压。程序将失去响应,因为在那个长时间运行的处理程序完成之前,无法处理任何消息。
示例:下面创建一个WPF程序,在主窗体中添加一个Label与一个Button,具体工作在后端代码的注释中
<Window x:Class="WpfForAsync.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfForAsync" mc:Ignorable="d" Title="MainWindow" Height="151.4" Width="323.2"> <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"/> </StackPanel> </Window>
后端代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace WpfForAsync { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void btnDoStuff_Click(object sender, RoutedEventArgs e) { //首先让按钮禁用 btnDoStuff.IsEnabled = false; //第二步将lable的文本改动 lblStatus.Content = "Doing Stuff"; //阻塞4秒钟 Thread.Sleep(1000 * 4); //将lable的内容改为原来的内容 lblStatus.Content = "Not Doing Anything"; //将按钮设置为可用 btnDoStuff.IsEnabled = true; } } }
事实上,当点击按钮的时候,窗体没有任何变化,Label也不会发生内容更改的情况,而且,主窗体在被阻塞的4秒内也无被冻结了!!!
来看程序执行的顺序:
由于7~10发生的太快,我们根本看不到发生了什么!
示例:使用异步来解决上述问题
后端代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace WpfForAsync { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private async void btnDoStuff_Click(object sender, RoutedEventArgs e) { //首先让按钮禁用 btnDoStuff.IsEnabled = false; //第二步将lable的文本改动 lblStatus.Content = "Doing Stuff"; await Task.Delay(1000 * 4); //将lable的内容改为原来的内容 lblStatus.Content = "Not Doing Anything"; //将按钮设置为可用 btnDoStuff.IsEnabled = true; } } }
将btnDoStuff_Click方法改为一个异步方法,异步方法内部使用await Task.Delay(1000 * 4);
,即将遇到await的时候,程序会返回到调用方法,处理程序将从处理器上摘下,禁用按钮、改变文本将被处理;当4秒之后,处理程序将自己再压入队列,再将之后的改变文本、启用按钮压入列队,当处理程序退出后,这两个子消息也将被执行,但在休息的4秒期间,是可以手动窗体的
Task.Yield方法可以创建一个立即返回的awaitable。等等一个Yield可以让异步方法在执行后续部分的同时返回到调用方法。可以将其理解成离开当前的消息队列,回到队列末尾,让处理器有时间处理其他任务。
示例:
using System; using System.Net; using System.Diagnostics; using System.Threading.Tasks; using System.Collections.Generic; using System.Threading; namespace CodeForAsync { static class DoStuff { public static async Task<int> FindSeriesSum(int x) { int sum = 0; for (int i = 1; i < x; i++) { sum += i; Console.WriteLine("Yield"+i); if (i % 10 == 0) await Task.Yield(); } return sum; } } class Program { private static void CountBig(int p) { for (int i = 0; i < p; i++) Console.WriteLine("CountBig"+i); } static void Main(string[] args) { Task<int> value = DoStuff.FindSeriesSum(1000); CountBig(1000); CountBig(1000); CountBig(1000); CountBig(1000); Console.WriteLine("Sum:{0}",value.Result); Console.ReadKey(); } } }
当程序遇到await Task.Yield();
时,会将当前的执行权转让出来,之后的CountBig(1000);
便可以提前得到执行
语法示例:
btn.Click += async (sender, e) => {//处理工作};
完整代码示例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace WpfForAsync { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.btnDoStuff.Click += async (sender, e) => { btnDoStuff.IsEnabled = false; lblStatus.Content = "Hello"; await Task.Delay(4000); btnDoStuff.IsEnabled = true; lblStatus.Content = "World"; }; } } }
一个完整的GUI示例,请自行尝试
<Window x:Class="WpfForAsync.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfForAsync" mc:Ignorable="d" Title="MainWindow" Height="151.4" Width="323.2" Loaded="Window_Loaded"> <StackPanel> <Button Name="btnProcess" Width="100" Click="btnProcess_Click">Process</Button> <Button Name="btnCancel" Width="100" Click="progressBar_Click" >Cancel</Button> <ProgressBar Name="progressBar" Height="20" Width="200" Margin="10" HorizontalAlignment="Right"></ProgressBar> </StackPanel> </Window>
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Threading.Tasks; namespace WpfForAsync { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { CancellationTokenSource _cancellationTokenSource; CancellationToken _cancellationToken; public MainWindow() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { } private async void btnProcess_Click(object sender, RoutedEventArgs e) { btnProcess.IsEnabled = false; _cancellationTokenSource = new CancellationTokenSource(); _cancellationToken = _cancellationTokenSource.Token; int completedPercent = 0; for (int i = 0; i < 10; i++) { if (_cancellationToken.IsCancellationRequested) break; try { await Task.Delay(500,_cancellationToken); completedPercent = (i+1) * 10; } catch (TaskCanceledException ex) { completedPercent = i * 10; } progressBar.Value = completedPercent; } string message = _cancellationToken.IsCancellationRequested ? string.Format("Process was cancelled at {0}%",completedPercent) : "Process completed normally"; MessageBox.Show(message,"Completion Status"); progressBar.Value = 0; btnProcess.IsEnabled = true; btnCancel.IsEnabled = true; } private void progressBar_Click(object sender, RoutedEventArgs e) { if (!btnProcess.IsEnabled) { btnCancel.IsEnabled = false; _cancellationTokenSource.Cancel(); } } } }