zoukankan      html  css  js  c++  java
  • To invoke and to begin invoke, that is a question.

    To invoke and to begin invoke, that is a question.

    在多线程的应用程序中经常会涉及到操作System.Windows.Forms.Control,我们经常会遇到一些常见的问题例如“为什么UI界面被挂起了,失去响应了”,等等。其实Control类已经提供了一套简单的机制来帮助我们处理这些问题,本文将会重点阐述该机制,对于一些常见问题进行解释。

    主要内容:

    • 基础概念。
    • 调用Invoke和BeginInvoke时,指定的Delegate会在那个线程运行?如何决定使用Invoke还是BeginInvoke?
    • BeginInvoke和EndInvoke必须成对使用吗?
    • 实现细节。

    基础概念

    .Net framework 定义了一个接口ISynchronizeInvoke来处理方法的同步及异步调用。

     1 public interface ISynchronizeInvoke
     2 {
     3     // Methods
     4     IAsyncResult BeginInvoke(Delegate method, object[] args);
     5     object EndInvoke(IAsyncResult result);
     6     object Invoke(Delegate method, object[] args);
     7 
     8     // Properties
     9     bool InvokeRequired { get; }
    10 }

    InvokeRequired:对于实现该接口的类型而言,当客户端尝试操作该类型的时候InvokeRequired将会要求客户端使用Invoke或者BeginInvoke方法来执行操作。
    而Invoke和BeginInvoke则分别以同步和异步的方式来进行操作。

    System.Windows.Forms.Control就实现了这个接口。

    调用Invoke和BeginInvoke时,指定的Delegate会在那个线程运行?如何决定使用Invoke还是BeginInvoke?

    在调用Invoke和BeginInvoke的时候,客户端必须提供一个指定的Delegate,那么这个Delegate是在那个线程执行那?
    MSDN的定义:
           -   Invoke, Executes the specified delegate on the thread that owns the control's underlying window handle.
           -   BeginInvoke,Executes the specified delegate asynchronously on the thread that the control's underlying handle was created on.

    从定义上我们可以看到Invoke和BeginInvoke都会在拥有控件Handle的线程上执行,也就是创建该控件的UI线程。

    看个例子:
    构建一个新的Windows Form, 添加一个Button控件到Form上,添加如下代码:

     1 public partial class Form1 : Form
     2 {
     3         private delegate void OutputMessage(string content);
     4         private IAsyncResult result = null;
     5         private System.Threading.Thread thread;
     6 
     7         public Form1()
     8         {
     9             InitializeComponent();
    10         }
    11 
    12         private void button1_Click(object sender, EventArgs e)
    13         {
    14             Console.WriteLine(string.Format("UI Thread Id : {0}", System.Threading.Thread.CurrentThread.ManagedThreadId));
    15 
    16             thread = new System.Threading.Thread(new System.Threading.ThreadStart(this.Test));
    17             thread.Start();
    18         }
    19 
    20         private void Test()
    21         {
    22             Console.WriteLine(string.Format("New Thread Id : {0}", System.Threading.Thread.CurrentThread.ManagedThreadId));
    23             Console.WriteLine("Start executing...");
    24             this.Invoke(new OutputMessage(this.WriteMessage), "Hello World!");
    25             //result = this.BeginInvoke(new OutputMessage(this.WriteMessage), "hello wrold!");
    26 
    27             Console.WriteLine("Finished executing...");
    28         }
    29 
    30         private void WriteMessage(string content)
    31         {
    32             Console.WriteLine(string.Format("Invoke  Thread Id : {0}", System.Threading.Thread.CurrentThread.ManagedThreadId));
    33             Console.WriteLine(content);
    34         }
    35 }

    点击Button控件,Console输出如下:

    UI Thread Id : 9
    New Thread Id : 10
    Start executing...
    Invoke Thread Id : 9
    Hello World!

    Finished executing...

    从上面的输出可以看出(蓝色部分在主线程,红色部分在新开启线程),尽管调用者是在一个新启动的线程中,但是Invoke中所指定Delegate是在主UI线程中执行的,而且是同步的,也就是说:只有“Hello World!”被输出后,新启动线程才会继续执行。

    将第24行代码注释掉,同时打开第25行代码,进行BeginInvoke调用,输出如下:

    UI Thread Id : 9
    New Thread Id : 10
    Start executing...
    Finished executing...

    Invoke Thread Id : 9
    Hello World!

    从上面的输出可以看出(蓝色部分在主线程,红色部分在新开启线程),在进行BeginInvoke调用的时候,指定的Delegate依然在主UI线程中运行。但是BeginInvoke并不会阻塞新开启的调用者线程,调用者线程将BeginInvoke交给执行线程后,会继续执行。所以在上面的例子中“Finished executing...”会先于“Hello World!”输出。

    以上就是Invoke与BeginInvoke的相同点与不同点,一个是同步执行,一个是异步执行,一个会堵塞调用者线程,一个不会。但都是在UI主线程(创建该控件的线程)上进行。

    BeginInvoke和EndInvoke必须成对使用吗?

    不需要, Control的BeginInvoke方法不需要在每次调用后,再调用相应的EndInvoke方法。EndInvoke方法的意图是获取异步执行方法的返回值,如果需要使用返回值,或者被refout标识的参数,则需要被调用。

    再看一下上面的例子,更改Test以及WriteMessage方法:

     1         private void Test()
     2         {
     3             Console.WriteLine(string.Format("New Thread Id : {0}", System.Threading.Thread.CurrentThread.ManagedThreadId));
     4             Console.WriteLine("Start executing...");
     5 
     6             result = this.BeginInvoke(new OutputMessage(this.WriteMessage), "Hello World!");
     7             Console.WriteLine("Finished executing...");
     8 
     9             Console.WriteLine("Start end invoke...");
    10             object retValue = this.EndInvoke(this.result);
    11             Console.WriteLine("Finished end invoke...");
    12         }
    13 
    14         private void WriteMessage(string content)
    15         {
    16             System.Threading.Thread.Sleep(5000);
    17             Console.WriteLine(string.Format("Invoke Thread Id : {0}", System.Threading.Thread.CurrentThread.ManagedThreadId));
    18             Console.WriteLine(content);
    19         }

    输出如下:

    UI Thread Id : 9
    New Thread Id : 10
    Start executing...
    Finished executing...
    Start end invoke...

    Invoke Thread Id : 9
    Hello World!

    Finished end invoke...

    在WriteMessage中,我们让当前线程Sleep 5秒。

    输出中蓝色的部分是在主UI线程上执行的,红色的部分是在新开启的线程上执行。可以看出调用EndInvoke之后,调用者线程会被堵塞,直至BeginInvoke所指定的方法执行完毕,也就是说如果EndInvoke是同步执行的,会引起线程堵塞。

    Control的BeginInvoke\EndInvoke与Delegate的BeginInvoke\EndInvoke是不相同的,Delegate要求BeginInvoke与EndInvoke成对调用,而Control无需这样做,从接口也可以看出来,Control的BeginInvoke不需要传入回调函数,而Delegate则是必须的。

    实现细节

    那么Control是如何实现Invoke和BeginInvoke的哪?

    首先我们要明白Windows窗口编程的一个规则,如果需要操作控件数据的话,只能通过创建该控件的线程来操作。这样做的目的是为了避免线程竞争,产生不可预知的错误。这也就是为什么在上面的例子中Invoke和BeginInvoke所指定的Delegate都会在主UI线程中执行的原因。

    那么调用者线程怎么样告诉UI线程来执行相应动作哪?当然是通过SendMessage或者PostMessage向UI线程发送消息来执行了。

    通过反编译Control的代码我们可以看到Invoke和BeginInvoke都是通过PostMessage来实现的,只不过Invoke调用的时候,调用者线程会持续等待直至Invoke结束。见下面的代码片段:

     1 private object MarshaledInvoke(Control caller, Delegate method, object[] args, bool synchronous)
     2 {
     3    //...向UI线程发送消息,该消息会在Control的WndProc中处理。
     4     UnsafeNativeMethods.PostMessage(new HandleRef(thisthis.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);
     5     
     6    //...
     7    // 如果是同步操作,则等待,否则之前已经返回。
     8     if (!entry.IsCompleted)
     9     {
    10         this.WaitForWaitHandle(entry.AsyncWaitHandle);
    11     }
    12    //...
    13 }

    全文完。

  • 相关阅读:
    ctypes运用
    win10 下获取不到
    semantic ui加载慢的问题
    python 将图片转换为base64编码转储进数据库
    循环遍历共享文件夹,不需要知道目录深度拷贝上传
    计划和打算
    pyqt按键检测
    python B+数的实现
    JAVA算数运算符
    ASCLL码表
  • 原文地址:https://www.cnblogs.com/tedzhao/p/1407577.html
Copyright © 2011-2022 走看看