C#中Control的Invoke与BeginInvoke在主副线程中的执行顺序和区别

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

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

Control.Invoke 方法 (Delegate) :在拥有此控件的基础窗口句柄的线程上执行指定的委托。
Control.BeginInvoke 方法 (Delegate) :在创建控件的基础句柄所在线程上异步执行指定委托。
 msdn说明: 控件上的大多数方法只能从创建控件的线程调用。 如果已经创建控件的句柄,则除了 InvokeRequired 属性以外,控件上还有四个可以从任何线程上安全调用的方法,它们是:Invoke、BeginInvoke、EndInvoke 和 CreateGraphics。 在后台线程上创建控件的句柄之前调用 CreateGraphics 可能会导致非法的跨线程调用。 对于所有其他方法调用,则应使用调用 (invoke) 方法之一封送对控件的线程的调用。 调用方法始终在控件的线程上调用自己的回调。
   

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

 代码如下 复制代码

/// <summary>
         /// 直接调用Invoke
         /// </summary>
         private void TestInvoke()
         {
             listBox1.Items.Add("--begin--");
             listBox1.Invoke(new Action(() =>
             {
                 listBox1.Items.Add("Invoke");
             }));
 
             Thread.Sleep(1000);
             listBox1.Items.Add("--end--");
         }

 

2.主线程调用BeginInvoke

 代码如下 复制代码
/// <summary>
         /// 直接调用BeginInvoke
         /// </summary>
         private void TestBeginInvoke()
         {
             listBox1.Items.Add("--begin--");
             var bi = listBox1.BeginInvoke(new Action(() =>
             {
                 //Thread.Sleep(10000);
                 listBox1.Items.Add("BeginInvoke");
             }));
             Thread.Sleep(1000);
             listBox1.Items.Add("--end--");
         }

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

  使用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。

 代码如下 复制代码

/// <summary>
         /// 线程调用Invoke
         /// </summary>
         private void ThreadInvoke()
         {
             listBox1.Items.Add("--begin--");
             new Thread(() =>
             {
                 Thread.CurrentThread.Name = "ThreadInvoke";
                 listBox1.Invoke(new Action(() =>
                     {
                         Thread.Sleep(10000);
                         this.listBox1.Items.Add("ThreadInvoke:" + Thread.CurrentThread.Name);                  
                     }));
             }).Start();
             Thread.Sleep(1000);
             listBox1.Items.Add("--end--");
         }
 
 
         private void button1_Click(object sender, EventArgs e)
         {
             Thread.CurrentThread.Name = "Main";
             ThreadInvoke();
         }
 
         private void button2_Click(object sender, EventArgs e)
         {
             listBox1.Items.Add("button2_Click");
         }

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

   

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

 

  4.支线线程调用BeginInvoke

 代码如下 复制代码

/// <summary>
         /// 线程调用BeginInvoke
         /// </summary>
         private void ThreadBeginInvoke()
         {
             listBox1.Items.Add("--begin--");
             new Thread(() =>
             {
                 Thread.CurrentThread.Name = "ThreadBeginInvoke";
                 Thread.Sleep(10000);
                 string temp = "Before!";
                 listBox1.BeginInvoke(new Action(() =>
                 {
                     this.listBox1.Items.Add(temp + ":" + Thread.CurrentThread.Name);
                 }));
                 temp += "After!";
             }).Start();
             Thread.Sleep(1000);
             listBox1.Items.Add("--end--");
         }
 
 
         private void button1_Click(object sender, EventArgs e)
         {
             Thread.CurrentThread.Name = "Main";
             ThreadBeginInvoke();
         }
 
         private void button2_Click(object sender, EventArgs e)
         {
             listBox1.Items.Add("button2_Click");
         }

输出:  

  

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

线程真正开始执行,是在创建它的线程结束后执行。
真正执行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将导致支线程阻塞主线程,该支线程就没有存在的意义。(当然有特殊需求除外)

时间: 2024-08-26 18:12:38

C#中Control的Invoke与BeginInvoke在主副线程中的执行顺序和区别的相关文章

链表-@数据结构大神,这个代码中,①、②、③执行顺序有区别嘛?为啥?

问题描述 @数据结构大神,这个代码中,①.②.③执行顺序有区别嘛?为啥? 解决方案 顺序错了的话(比如先操作③的话),有可能丢失后面的节点

如何在普通类实例的线程过程中,同步调用执行在类实例自身所在的原来的那个线程中的方法

问题描述 如何在普通类实例的线程过程中,同步调用执行在类实例自身所在的原来的那个线程中的方法如后代码,是一个常见的实例,讲的是通过Control.Invoke在线程函数中,同步调用窗体主线程中的Form1实例的普通方法txt.但问题是,很多时候我们自己自定义的类,并不是从Control类继承的,从而也没有这个功能的Invoke方法供调用,这种类要怎么设计呢?虽然说用的示例代码是vb.net的,但严格来说,这个和语言无关,是一个.net开发的基本问题.ImportsSystemImportsSys

JAVA之旅(十四)——静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制

JAVA之旅(十四)--静态同步函数的锁是class对象,多线程的单例设计模式,死锁,线程中的通讯以及通讯所带来的安全隐患,等待唤醒机制 JAVA之旅,一路有你,加油! 一.静态同步函数的锁是class对象 我们在上节验证了同步函数的锁是this,但是对于静态同步函数,你又知道多少呢? 我们做一个这样的小实验,我们给show方法加上static关键字去修饰 private static synchronized void show() { if (tick > 0) { try { Thread

Invoke或者BeginInvoke的使用(转)

在Invoke或者BeginInvoke的使用中无一例外地使用了委托Delegate,至于委托的本质请参考我的另一随笔:  一.为什么Control类提供了Invoke和BeginInvoke机制? 关于这个问题的最主要的原因已经是dotnet程序员众所周知的,我在此费点笔墨再次记录到自己的日志,以便日后提醒一下自己. 1.windows程序消息机制 Windows GUI程序是基于消息机制的,有个主线程维护着一个消息泵.这个消息泵让windows程序生生不息.                 

在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke

问题描述 线程中每10ms就更新一次主界面的lable控件,使用的事件+委托更新.主界面自己没有更新时没有问题.但是主界面自己更新其中的gridview(非线程要更新的控件)时,就会报在创建窗口句柄之前,不能在控件上调用Invoke或BeginInvoke错误.查了下网上的解决办法,加了while(!this.IsHandleCreated){;},还是报错,不管用,如何解决还得请求帮助.谢谢! 解决方案 解决方案二:自己更新还要什么Invoke

在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke。

本文转载:http://blog.csdn.net/playing9c/article/details/7471918              http://blog.csdn.net/beelinkerlidejun/article/details/4772491              http://www.cnblogs.com/fish124423/archive/2012/10/16/2726543.html C#窗体的多线程一直是个难题,总是要出现奇奇怪怪的错误.今天开发alex

在C#中通过P/Invoke调用Win32DLL

  我在自己最近的编程中注意到一个趋势,正是这个趋势才引出本月的专栏主题.最近,我在基于 Microsoft .Net Framework 的应用程序中完成了大量的 Win32 Interop.我并不是要说我的应用程序充满了自定义的 interop 代码,但有时我会在 .Net Framework 类库中碰到一些次要但又繁絮.不充分的内容,通过调用该 Windows API,可以快速减少这样的麻烦. 因此我认为,.Net Framework 1.0 或 1.1 版类库中存在任何 Windows

TX Text Control文字处理教程(12)MS Word中字段的导入导出操作

Text Control 中的域可以与MS Word中的域进行相互的导入和导出操作,Text Control中的ApplicationField类为获取/设置域的数据/参数提供了相应的公共接口.下面将结合MS Word中最常用的MERGEFIELD 和 FORMTEXTBOX功能来讲解域相关的操作. 相应的源代码可以在TX Text Control.NET的安装目录中找到:         Samples\WinForms\VB.NET\ ApplicationFields        

c#Sytem.Threading.Timer线程TimerCallBack委托中方法涉及访问ui成员用考虑跨线程安全问题吗

问题描述 publicpartialclassScreen1{privateSystem.Threading.Timert1;voidScreen1_Opened(System.Objectsender,System.EventArgse){t1=newSystem.Threading.Timer(newTimerCallback(countTimer),null,0,1000);//定时器线程}voidcountTimer(objectob){this.Text1.Text="hello&qu