多线程之旅七——GUI线程模型,消息的投递(post)与处理

摘要:
基于消息的GUI架构在过去几天里,大多数编程语言平台的GUI架构几乎没有改变。所有后续的GUI事件,例如用户生成的事件、系统生成的事件以及应用程序中自定义组件的特定事件,都将被传递到此消息队列以供实现。这样,无论GUI线程是否包含在线程模型中,都可以正确调用它们。

基于消息的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!";
            }
        }
    }

免责声明:文章转载自《多线程之旅七——GUI线程模型,消息的投递(post)与处理》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇实现定时任务Quartz替代TimerMaven使用--打包和运行下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

iOS之多线程浅谈

1)并发和并行的区别 在软件开发中不可避免的会遇到多线程的问题,在iOS客户端开发(或者.NET的winform或者wpf这样的cs程序)中就更不可避免的会用到多线程,在bs类型的web项目中要考虑一个并发问题,而在这里我们来说一下多线程的并行问题。 首先了解并发和并行的区别: 并发的实质是一个物理CPU(也可以多个物理CPU) 在若干道程序之间多路复用,...

xios封装

封装的意义 1.提高代码可读性2.提高代码可维护性3.减少代码书写 封装 import axios from 'axios' axios.defaults.baseURL = 'http://127.0.0.1:8000' // 全局设置网络超时 axios.defaults.timeout = 10000; //设置请求头信息 axios.defau...

Android优化总结

极力推荐文章:欢迎收藏Android 干货分享 文章转载网络 原文地址如下:https://juejin.im/post/5d072dbc51882540b7104709 1.OOM和崩溃优化 1.2 ANR优化 ANR的产生需要满足三个条件 主线程:只有应用程序进程的主线程响应超时才会产生ANR; 超时时间:产生ANR的上下文不同,超时时间也会...

Git GUI如何汉化设置成中文?

Git bash的汉化很简单,只需要在命令行界面点击右键-选择option-Windows-然后在里面将language设置成中文就可以了。   上图是设置好的界面。 但是反观Git GUI,想要设置成中文就显得没那么容易了。打开设置里面,全是密密麻麻的的英文,而且找了半天也没找到对应的语言设置,那么到底应该怎么设置Git GUI的中文显示呢? 其实Git...

HBase的Write Ahead Log (WAL) —— 整体架构、线程模型

解决的问题 HBase的Write Ahead Log (WAL)提供了一种高并发、持久化的日志保存与回放机制。每一个业务数据的写入操作(PUT / DELETE)执行前,都会记账在WAL中。 如果出现HBase服务器宕机,则可以从WAL中回放执行之前没有完成的操作。 本文主要探讨HBase的WAL机制,如何从线程模型、消息机制的层面上,解决这些问题: 1...

生产者和消费者模型

生产者和消费者模型 线程通信:不同的线程执行不同的任务,如果这些任务有某种关系,各个线程必须要能够通信,从而完成工作。线程通信中的经典问题:生产者和消费者问题 模型: 这个模型也体现了面向对象的设计理念:低耦合 也就是为什么生产者生产的东西为什么不直接给消费者,还有经过一个缓冲区(共享资源区) 这就相当于去包子店吃包子,你要5个包子,老板把5个人包子放在...