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


    基于消息的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!";
                }
            }
        }
  • 相关阅读:
    IDEA下Maven的pom文件导入依赖出现Auto build completed with errors
    org.apache.jasper.JasperException: java.lang.NullPointerException
    Eclipse下导入web项目(Some projects cannot be imported because they already exist in the workspace)
    JS中构造函数的方法定义在原型对象里
    JS变量赋值
    JDBC以及连接池连接MySQL出现时区错误问题
    chrome中如何截取整个网页
    java中final、super、static关键字的使用
    初识less
    根据当前时间显示问候语
  • 原文地址:https://www.cnblogs.com/lwzz/p/2754011.html
Copyright © 2020-2023  润新知