• Invoke和BeginInvoke


    今天无意中看到有关Invoke和BeginInvoke的一些资料,不太清楚它们之间的区别。所以花了点时间研究了下。

      据msdn中介绍,它们最大的区别就是BeginInvoke属于异步执行的。

    • Control.Invoke 方法 (Delegate) :在拥有此控件的基础窗口句柄的线程上执行指定的委托。
    • Control.BeginInvoke 方法 (Delegate) :在创建控件的基础句柄所在线程上异步执行指定委托。
     msdn说明:

    控件上的大多数方法只能从创建控件的线程调用。 如果已经创建控件的句柄,则除了 InvokeRequired 属性以外,控件上还有四个可以从任何线程上安全调用的方法,它们是:InvokeBeginInvokeEndInvoke 和 CreateGraphics 在后台线程上创建控件的句柄之前调用CreateGraphics 可能会导致非法的跨线程调用。 对于所有其他方法调用,则应使用调用 (invoke) 方法之一封送对控件的线程的调用。 调用方法始终在控件的线程上调用自己的回调。

      

      于是用下面的代码进行初步的测试:  

      1.主线程调用Invoke   

    复制代码
     1         /// <summary>
     2         /// 直接调用Invoke
     3         /// </summary>
     4         private void TestInvoke()
     5         {
     6             listBox1.Items.Add("--begin--");
     7             listBox1.Invoke(new Action(() =>
     8             {
     9                 listBox1.Items.Add("Invoke");
    10             }));
    11 
    12             Thread.Sleep(1000);
    13             listBox1.Items.Add("--end--");
    14         }
    复制代码

    输出:    

      

      从输出结果上可以看出,Invoke被调用后,是马上执行的。这点很好理解。

      2.主线程调用BeginInvoke

    复制代码
     1         /// <summary>
     2         /// 直接调用BeginInvoke
     3         /// </summary>
     4         private void TestBeginInvoke()
     5         {
     6             listBox1.Items.Add("--begin--");
     7             var bi = listBox1.BeginInvoke(new Action(() =>
     8             {
     9                 //Thread.Sleep(10000);
    10                 listBox1.Items.Add("BeginInvoke");
    11             }));
    12             Thread.Sleep(1000);
    13             listBox1.Items.Add("--end--");
    14         }
    复制代码

    输出:  

      

      从输出能看出,只有当调用BeginInvoke的线程结束后,才执行它的内容。

      不过有两种情况下,它会马上执行:

      使用EndInvoke,检索由传递的 IAsyncResult 表示的异步操作的返回值。

    复制代码
            /// <summary>
            /// 调用BeginInvoke、EndInvoke
            /// </summary>
            private void TestBeginInvokeEndInvoke()
            {
                listBox1.Items.Add("--begin--");
                var bi = listBox1.BeginInvoke(new Action(() =>
                {
                    Thread.Sleep(1000);
                    listBox1.Items.Add("BeginInvokeEndInvoke");
                }));
                listBox1.EndInvoke(bi);
                listBox1.Items.Add("--end--");
            }
    复制代码

    输出:  

      

       

      同一个控件调用Invoke时,会马上执行先前的BeginInvoke

    复制代码
            /// <summary>
            /// 调用BeginInvoke、Invoke
            /// </summary>
            private void TestBeginInvokeInvoke()
            {
                listBox1.Items.Add("--begin--");
                listBox1.BeginInvoke(new Action(() =>
                    {
                        Thread.Sleep(1000);
                        listBox1.Items.Add("BeginInvoke");
                    }));
                listBox1.Invoke(new Action(() =>
                    {
                        listBox1.Items.Add("Invoke");
                    }));
                listBox1.Items.Add("--end--");
            }
    复制代码

    输出:

      

      注:在主线程中直接调用Invoke、BeginInvoke、EndInvoke都会造成阻塞。所以应该利用副线程(支线线程)调用。

      3.支线线程调用Invoke

      创建一个线程,并在线程中调用Invoke,同时测试程序是在哪个线程中调用Invoke。 

    复制代码
     1         /// <summary>
     2         /// 线程调用Invoke
     3         /// </summary>
     4         private void ThreadInvoke()
     5         {
     6             listBox1.Items.Add("--begin--");
     7             new Thread(() =>
     8             {
     9                 Thread.CurrentThread.Name = "ThreadInvoke";
    10                 listBox1.Invoke(new Action(() =>
    11                     {
    12                         Thread.Sleep(10000);
    13                         this.listBox1.Items.Add("ThreadInvoke:" + Thread.CurrentThread.Name);                   
    14                     }));
    15             }).Start();
    16             Thread.Sleep(1000);
    17             listBox1.Items.Add("--end--");
    18         }
    19 
    20 
    21         private void button1_Click(object sender, EventArgs e)
    22         {
    23             Thread.CurrentThread.Name = "Main";
    24             ThreadInvoke();
    25         }
    26 
    27         private void button2_Click(object sender, EventArgs e)
    28         {
    29             listBox1.Items.Add("button2_Click");
    30         }
    复制代码

    输出:

      

      当点击button1后,我试图去点击button2,却发现程序被阻塞了。可见Invoke尽管在支线程中调用,实际上仍然在拥有此控件的基础窗口句柄的线程上执行。

       

      接着来测试下在支线程中调用BeginInvoke.

      4.支线线程调用BeginInvoke

    复制代码
     1         /// <summary>
     2         /// 线程调用BeginInvoke
     3         /// </summary>
     4         private void ThreadBeginInvoke()
     5         {
     6             listBox1.Items.Add("--begin--");
     7             new Thread(() =>
     8             {
     9                 Thread.CurrentThread.Name = "ThreadBeginInvoke";
    10                 Thread.Sleep(10000);
    11                 string temp = "Before!";
    12                 listBox1.BeginInvoke(new Action(() =>
    13                 {
    14                     this.listBox1.Items.Add(temp + ":" + Thread.CurrentThread.Name);
    15                 }));
    16                 temp += "After!";
    17             }).Start();
    18             Thread.Sleep(1000);
    19             listBox1.Items.Add("--end--");
    20         }
    21 
    22 
    23         private void button1_Click(object sender, EventArgs e)
    24         {
    25             Thread.CurrentThread.Name = "Main";
    26             ThreadBeginInvoke();
    27         }
    28 
    29         private void button2_Click(object sender, EventArgs e)
    30         {
    31             listBox1.Items.Add("button2_Click");
    32         }
    复制代码

    输出:  

      

      从这结果中我们能得出以下几点:

    • 线程真正开始执行,是在创建它的线程结束后执行。
    • 真正执行BeginInvoke的线程就是创建控件线程上。
    • BeginInvoke在线程中调用时,是异步执行的而且没有对主线程造成阻塞。(button2_Click在Before!After!:Main前面)
    • BeginInvoke只有当创建它的线程结束后执行。(当然你也能通过EndInvoke、Invoke让其提早执行。)

    总结:  

      以下为了方便理解,假设如下:

        主线程表示Control.Invoke或Control.BeginInvoke中Control所在的线程,即创建该创建的线程。(一般为UI线程)

        支线程表示调用Invoke或BeginInvoke的线程。

    • Invoke、BeginInvoke始终在主线程中执行。
    • Invoke被调用时就会直接执行,也就是直接阻塞线程(包括主支线程),直到它结束。而BeginInvoke只有等支线程结束或者调用EndInvoke、Invoke时才会开始执行。
    • Invoke不管在哪里执行都会造成主线程的阻塞。而BeginInvoke只会阻塞支线程,而对于主线程是异步执行。(注意,如果在主线程中调用,也会阻塞主线程)。
    • 在支线程中,应该使用BeginInvoke,否则调用Invoke将导致支线程阻塞主线程,该支线程就没有存在的意义。(当然有特殊需求除外)

                                           SamWang

                                           2012-05-25

    作者:SamWang 
    出处:http://wangshenhe.cnblogs.com/ 
    本文版权归作者和博客园共有,欢迎围观转载。转载时请您务必在文章明显位置给出原文链接,谢谢您的合作。

  • 相关阅读:
    python简单接口的测试(随机数等)
    关于数据库的去重+导入导出参数
    找到并杀死一个软件开启的进程
    blinker库
    HTTP状态码
    一致性哈希算法
    celery
    项目部署
    redis更多
    functools模块
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2517916.html
Copyright © 2020-2023  润新知