基于消息的GUI构架
在过去的日子中,大部分编程语言平台的GUI构架几乎没有发生变化。虽然在细节上存在一些差异,比如在功能和编程风格上,但大部分都是采用了相同的构架来响应用户输入以及重新绘制屏幕。这种构架可以被总结为“单线程且基于消息”。
Message msg;
While(GetMessage(msg))
{
TranslateMessage(msg);
DispatchMessage(msg);
}
这段代码可以称为消息循环。在这个循环中,执行顺序是串行的,一个GetMessage只能在前一个GetMessage执行完以后才能执行。
拿WPF或WindowsForm举例,每个线程至少会创建一个拥有消息列队的窗口,并且这个线程的任务之一就是处理列队中的各个消息。只要在应用程序中调用了Application.Run,那么执行这个方法的线程就默认被赋予了这个任务。随后的所有GUI事件,例如用户引发的事件(点击按钮,关闭窗口等),系统引发的事件(重绘窗口,调整大小等),以及应用程序中自定义组件的特定事件等,都将把相应的消息投递给这个消息列队来实现。这意味着,在调用了run之后,随后发生的大部分工作都是由事件处理器为了响应GUI事件而生成的。
如图:
GUI线程
Gui线程负责取走(get)和分发(dispatch)消息,同时负责描绘界面,如果GUI线程阻塞在分发处理消息这一步的话,那么消息就会在消息队列中积累起来,并等待GUI线程回到消息列队来。
如果阻塞的是一个长时间的操作,比如下载一个文件的话,假设10秒钟,那么用户在10秒钟内都不能进行任何操作,因为线程没法获取新的消息进行处理。
这就是为什么在Windows中存在着MsgWaitForMultipleObjects的原因,这个API使得线程在等待的同时仍然可以运行消息循环。在.NET中,你连这个选择都没有。
消息分发时要考虑到复杂的重入性问题,很难确保一个事件处理器阻塞时,可以安全分发其他GUI事件以响应消息。
因此,一种相对而言更容易掌握的解决方法就是只有在GUI线程中的代码才能够操纵GUI控件,在更新GUI时所需要的其他数据和计算都必须在其他线程中完成,而不是在GUI线程上。如图:
通常这意味着把工作转交给线程池完成,然后在得到结果后把结果合并回GUI线程上。这也就是我们接下来要介绍的两个类。
SynchronizationContext 和 BackgroundWorker
SynchronizationContext 对不同线程间的调度操作进行同步,把一些异步操作的结果Post回GUI线程里。
WPF中DispatcherSynchronizationContext的实现
public override void Post(SendOrPostCallback d, object state) { _dispatcher.Beginlnvoke(DispatcherPriority.Normal, d, state); } public override void Send(SendOrPostCallback d, object state) { _dispatcher.lnvoke(DispatcherPriority.Normal, d, state); }
有些情况下,如在控制台中我们不能通过SynchronizationContext类的Current属性获取SynchronizationContext实例,我们包装了一下这个方法。
private static AsyncCallback SyncContextCallback(AsyncCallback callback) { // Capture the calling thread's SynchronizationContext-derived object SynchronizationContext sc = SynchronizationContext.Current; // If there is no SC, just return what was passed in if (sc == null) return callback; // Return a delegate that, when invoked, posts to the captured SC a method that // calls the original AsyncCallback passing it the IAsyncResult argument return asyncResult => sc.Post(result => callback((IAsyncResult)result), asyncResult); }
这个方法将一个普通的AsyncCallback方法转换成特殊的AsyncCallback 方法,它通过SynchronizationContext 来调用。这样无论线程模型中是否含有GUI线程,都可以正确的调用。
internal sealed class MyWindowsForm : Form { public MyWindowsForm() { Text = "Click in the window to start a Web request"; Width = 400; Height = 100; } protected override void OnMouseClick(MouseEventArgs e) { // The GUI thread initiates the asynchronous Web request Text = "Web request initiated"; var webRequest = WebRequest.Create("http://Wintellect.com/"); webRequest.BeginGetResponse(SyncContextCallback(ProcessWebResponse), webRequest); base.OnMouseClick(e); } private void ProcessWebResponse(IAsyncResult result) { // If we get here, this must be the GUI thread, it's OK to update the UI var webRequest = (WebRequest)result.AsyncState; using (var webResponse = webRequest.EndGetResponse(result)) { Text = "Content length: " + webResponse.ContentLength; } } }
这其实就是AsyncOperationManager的基本原理。
public static class AsyncOperationManager { public static SynchronizationContext { getj setj } public static AsyncOperation CreateOperation( object userSuppliedState ) ; }
BackGroundWorker是在前面所说的基础上构建起来的更高层次的抽象,它对GUI程序中一些最常用的操作给出了规范的定义。有三个事件:
DoWork 、ProgressChanged 和 RunWorkerCompleted
在程序中调用RunWorkerAsync方法则会启动DoWork事件的事件处理,当在事件处理过程中,调用 ReportProgress方法则会启动ProgressChanged事件的事件处理,而当DoWork事件处理完成时,则会触发 RunWorkerCompleted事件。
public partial class Form1 : Form { public Form1() { InitializeComponent(); backgroundWorker1.WorkerReportsProgress = true; backgroundWorker1.WorkerSupportsCancellation = true; } private void startAsyncButton_Click(object sender, EventArgs e) { if (backgroundWorker1.IsBusy != true) { // Start the asynchronous operation. backgroundWorker1.RunWorkerAsync(); } } private void cancelAsyncButton_Click(object sender, EventArgs e) { if (backgroundWorker1.WorkerSupportsCancellation == true) { // Cancel the asynchronous operation. backgroundWorker1.CancelAsync(); } } // This event handler is where the time-consuming work is done. private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker worker = sender as BackgroundWorker; for (int i = 1; i <= 10; i++) { if (worker.CancellationPending == true) { e.Cancel = true; break; } else { // Perform a time consuming operation and report progress. System.Threading.Thread.Sleep(500); worker.ReportProgress(i * 10); } } } // This event handler updates the progress. private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) { resultLabel.Text = (e.ProgressPercentage.ToString() + "%"); } // This event handler deals with the results of the background operation. private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Cancelled == true) { resultLabel.Text = "Canceled!"; } else if (e.Error != null) { resultLabel.Text = "Error: " + e.Error.Message; } else { resultLabel.Text = "Done!"; } } }