Invoke和BeginInvoke 详细介绍

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

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

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

控件上的大多数方法只能从创建控件的线程调用。 如果已经创建控件的句柄,则除了 InvokeRequired 属性以外,控件上还有四个可以从任何线程上安全调用的方法,它们是:InvokeBeginInvokeEndInvokeCreateGraphics 在后台线程上创建控件的句柄之前调用 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                 string temp = "Before!";
11                 listBox1.Invoke(new Action(() =>
12                     {
13                         Thread.Sleep(10000);
14                         this.listBox1.Items.Add(temp +="Invoke!" + Thread.CurrentThread.Name);
15                     }));
16                 temp += "After!";
17             }).Start();
18             listBox1.Items.Add("--end--");          
19         }
20 
21 
22         private void button1_Click(object sender, EventArgs e)
23         {
24             Thread.CurrentThread.Name = "Main";
25             ThreadInvoke();
26         }
27 
28         private void button2_Click(object sender, EventArgs e)
29         {
30             listBox1.Items.Add("button2_Click");
31         }
复制代码

 

输出:  

  

  • Invoke的委托在主线程中执行
  • Invoke在支线程中调用也会阻塞主线程。(当点击button1后,我试图去点击button2,却发现程序被阻塞了)
  • Invoke还会阻塞支线程。(因为输出结果中没有出现After)  

  接着来测试下在支线程中调用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                 string temp = "Before!";
11                 listBox1.BeginInvoke(new Action(() =>
12                 {
13                     Thread.Sleep(10000); 14                     this.listBox1.Items.Add(temp += "Invoke!" + Thread.CurrentThread.Name);
15                 }));
17                 temp += "After!";
18             }).Start();
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在支线程中调用也会阻塞主线程。
  • BeginInvoke相对于支线程是异步的。

 

总结:  

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

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

    支线程表示不同于主线程的调用Invoke或BeginInvoke的线程。

  • Control的Invoke和BeginInvoke的委托方法是在主线程,即UI线程上执行。(也就是说如果你的委托方法用来取花费时间长的数据,然后更新界面什么的,千万别在主线程上调用Control.Invoke和Control.BeginInvoke,因为这些是依然阻塞UI线程的,造成界面的假死)
  • Invoke会阻塞主支线程,BeginInvoke只会阻塞主线程,不会阻塞支线程!因此BeginInvoke的异步执行是指相对于支线程异步,而不是相对于主线程异步。(从最后一个例子就能看出,程序运行点击button1)

                                       SamWang

                                      2012-05-25

 

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

 

  

 

 

  

  

 
 
分类: C#, 原创
 
 
+加关注
8
0
 
    (请您对文章做出评价)   
 
« 上一篇: 【笔记】《编写高质量代码:改善c#程序的157个建议》-第1章 基本语言要素(SamWang) » 下一篇: [转]VS隐藏的快捷键和小功能
posted on 2012-05-25 09:28 SamWang 阅读( 2760) 评论( 11编辑 收藏

 
评论:
 
#1楼 2012-05-25 11:27 | Tommy_金博
分析的好~感谢分享
 
#2楼 2012-05-25 13:17 | fupei101011
引用Invoke被调用时就会直接执行,也就是直接阻塞线程(包括主支线程),直到它结束。而BeginInvoke只有等支线程结束或者调用EndInvoke、Invoke时才会开始执行。假如这里所谓的“主线程”是指创建控件的线程。所谓“支线程”是指不同于主线程而且是调用begininvoke/invoke的线程。 beginInvoke和invoke所执行的委托是在主线程中执行的。但invoke会阻塞线程,beginInvoke则不会。他们将到windows消息队列中排队等待。所以beginInvoke之后,该委托将和支线程之后的代码并发执行,而不存在你所说的先后问题。该委托在主线程中执行所以也不存在什么阻塞主线程这个说法。 至于在运行结果中出现了 Before!After!Main ,这并能说明BeginInvoke的委托一定是在线程结束以后执行。出现这个结果的主要原因在于引用Thread.Sleep(1000);这句代码,让主线程睡眠了一秒钟。正如前面所说,委托在主线程windows消息队列中,主线程睡眠,它当然要等一秒后,直到当前事件button1_Click执行完了,才能可能轮到他。而支线程恰好用着一秒中时间执行完了。所以让你误认为是beginInvoke回调的委托肯定是在支线程执行完后才会执行。你可以在引用temp += "After!";之前加Thread.Sleep(10000);结果很可能就是Before!Main!
 
#3楼 2012-05-25 14:06 | smark
其实没有必要这样测试去判断,看下代码就最了解了
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[EditorBrowsable(EditorBrowsableState.Advanced)]
public IAsyncResult BeginInvoke(Delegate method, params object [] args)
{
    IAsyncResult result;
    using ( new Control.MultithreadSafeCallScope())
    {
        Control control = this .FindMarshalingControl();
        result = (IAsyncResult)control.MarshaledInvoke( this , method, args, false );
    }
    return result;
}
 
// System.Windows.Forms.Control
public object Invoke(Delegate method, params object [] args)
{
    object result;
    using ( new Control.MultithreadSafeCallScope())
    {
        Control control = this .FindMarshalingControl();
        result = control.MarshaledInvoke( this , method, args, true );
    }
    return result;
}
再细看一下MarshaledInvoke代码就知道都是做了些什么
http://pic.cnitblog.com/face/u254151.png?id=10173521 
#4楼 [ 楼主] 2012-05-25 14:52 | SamWang
@smark 源代码哪里来的?能提供一份吗?
http://pic.cnitblog.com/face/u406206.jpg?id=16235606 
#5楼 2012-05-25 14:56 | smark
@SamWang 用ILSpy反一下就清楚了
http://pic.cnitblog.com/face/u254151.png?id=10173521 
#6楼 2012-05-25 16:15 | fupei101011
@SamWang 引用SamWang: @fupei101011 感谢您的提醒!我觉得问题可能是出在this.listBox1.Items.Add()上。 我用新的代码测试了下,BeginInvoke确实是调用后直接异步执行的。 [code=csharp]         private void TestBeginInvoke()         {             string temp = "begin";             listBox1.BeginInvoke(new Action(() =>             {                 temp = "BeginInvok...具体不清楚这个函数是在哪里调用的,所以不好评论。
 
#7楼 [ 楼主] 2012-05-25 16:31 | SamWang
@fupei101011 引用fupei101011: @SamWang引用SamWang: @fupei101011 感谢您的提醒!我觉得问题可能是出在this.listBox1.Items.Add()上。 我用新的代码测试了下,BeginInvoke确实是调用后直接异步执行的。 [code=csharp]         private void TestBeginInvoke()         {             string temp = "begin";             listBox1.BeginInvoke(new Action(() =>             {      ...哎,晕死!点错了!评论被我删除了! 其实我上面的回复有问题的。 上面的代码有问题。实际上BeginInvoke在主线程中还是最后执行的。
Thread.Sleep(1000); temp += "After!"; 倒是最后那个例子确实有问题,加上Thread.Sleep(1000);后输出的结果为Before!:Main. 确实是因为主线程睡眠后,使得BeginInvoke的调用被延迟了! 多谢提醒!
http://pic.cnitblog.com/face/u406206.jpg?id=16235606 
#8楼 [ 楼主] 2012-05-25 16:44 | SamWang
@fupei101011 引用fupei101011:引用Invoke被调用时就会直接执行,也就是直接阻塞线程(包括主支线程),直到它结束。而BeginInvoke只有等支线程结束或者调用EndInvoke、Invoke时才会开始执行。假如这里所谓的“主线程”是指创建控件的线程。所谓“支线程”是指不同于主线程而且是调用begininvoke/invoke的线程。 beginInvoke和invoke所执行的委托是在主线程中执行的。但invoke会阻塞线程,beginInvoke则不会。他们将到windows消息队列中排队等待。所以beginInvoke之后,该委托将和支线程之后的代码并发执行,...请教个问题,您这里提到关于windows消息队列的问题。此处我该如何证明BeginInvoke不是在主线程结束后才执行的? 假如主线程有A/B两段代码、支线程中的BeginInvoke假设为C代码。 当A代码进入队列后,这时支线程也并发执行,将BeginInvoke的代码加入队列。 那么主线程代码执行的顺序是不是A--C--B? 这如何证明?
http://pic.cnitblog.com/face/u406206.jpg?id=16235606 
#9楼 2012-05-31 16:09 | 滴答的雨
引用SamWang: @fupei101011引用fupei101011:引用Invoke被调用时就会直接执行,也就是直接阻塞线程(包括主支线程),直到它结束。而BeginInvoke只有等支线程结束或者调用EndInvoke、Invoke时才会开始执行。假如这里所谓的“主线程”是指创建控件的线程。所谓“支线程”是指不同于主线程而且是调用begininvoke/invoke的线程。 beginInvoke和invoke所执行的委托是在主线程中执行的。但invoke会阻塞线程,beginInvoke则不会。他们将到windows消息队列中排队等待。所以beg...开始我是这样想的(因为委托的Invoke和BeginInvoke是这样的) BeginInvoke里面启用了一个新线程在执行任务,可在BeginInvoke里面输出Thread.CurrentThread当前线程id和外面主线程id比对下就明白咯。      异步线程本身调度是由windows控制的,如果你是单核那么就是同步了;多核的话当有空余线程才回立即执行。 后来验证下错咯,并且也忽略了控件只能在他所创建的线程(“主线程”)上向windows队列插入消息。
后面用reflect找到底层实现看了下是这样的:      对于控件的Invoke和BeginInvoke的同步和异步只是说如何将要执行的操作放入windows消息队列的,注意:在调用到放入windows队列过程中一直使用的是主线程。(1)Invoke在放入后会AsyncWaitHandle即等待方法执行完再退出(2)BeginInvoke则放入后直接退出了。
另外主线程的执行顺序不管Invoke还是BeginInvoke肯定是:A--C--B

转载于:https://www.cnblogs.com/SupremeGISER/p/3439569.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值