Invoke 和 BeginInvoke 的区别-程序员宅基地

技术标签: ui  操作系统  数据结构与算法  

在Invoke或者BeginInvoke的使用中无一例外地使用了委托Delegate。 

一、为什么Control类提供了Invoke和BeginInvoke机制?

关于这个问题的最主要的原因已经是dotnet程序员众所周知的,我在此费点笔墨再次记录到自己的日志,以便日后提醒一下自己。

1、windows程序消息机制

Windows GUI程序是基于消息机制的,有个主线程维护着一个消息泵。这个消息泵让windows程序生生不息。

                                                  Windows GUI程序的消息循环

 

 

Windows程序有个消息队列,窗体上的所有消息是这个队列里面消息的最主要来源。这里的while循环使用了GetMessage()这个方法,这是个阻塞方法,也就是队列为空时方法就会被阻塞,从而这个while循环停止运动,这避免了一个程序把cpu无缘无故地耗尽,让其它程序难以得到响应。当然在某些需要cpu最大限度运动的程序里面就可以使用另外的方法,例如某些3d游戏或者及时战略游戏中,一般会使用PeekMessage()这个方法,它不会被windows阻塞,从而保证整个游戏的流畅和比较高的帧速。

这个主线程维护着整个窗体以及上面的子控件。当它得到一个消息,就会调用DispatchMessage方法派遣消息,这会引起对窗体上的窗口过程的调用。窗口过程里面当然是程序员提供的窗体数据更新代码和其它代码。

2、dotnet里面的消息循环

public static void Main(string[] args)

{

   Form f = new Form();

   Application.Run(f);

}

Dotnet窗体程序封装了上述的while循环,这个循环就是通过Application.Run方法启动的。

3、线程外操作GUI控件的问题

如果从另外一个线程操作windows窗体上的控件,就会和主线程产生竞争,造成不可预料的结果,甚至死锁。因此windows GUI编程有一个规则,就是只能通过创建控件的线程来操作控件的数据,否则就可能产生不可预料的结果。

因此,dotnet里面,为了方便地解决这些问题,Control类实现了ISynchronizeInvoke接口,提供了Invoke和BeginInvoke方法来提供让其它线程更新GUI界面控件的机制。

public interface ISynchronizeInvoke

{

        [HostProtection(SecurityAction.LinkDemand, Synchronization=true, ExternalThreading=true)]

        IAsyncResult BeginInvoke(Delegate method, object[] args);

        object EndInvoke(IAsyncResult result);

        object Invoke(Delegate method, object[] args);

        bool InvokeRequired { get; }

}

}

如果从线程外操作windows窗体控件,那么就需要使用Invoke或者BeginInvoke方法,通过一个委托把调用封送到控件所属的线程上执行。

二、消息机制---线程间和进程间通信机制

1、window消息发送

Windows消息机制是windows平台上的线程或者进程间通信机制之一。Windows消息值其实就是定义的一个数据结构,最重要的是消息的类型,它就是一个整数;然后就是消息的参数。消息的参数可以表示很多东西。

Windows提供了一些api用来向一个线程的消息队列发送消息。因此,一个线程可以向另一个线程的消息队列发送消息从而告诉对方做什么,这样就完成了线程间的通信。有些api发送消息需要一个窗口句柄,这种函数可以把消息发送到指定窗口的主线程消息队列;而有些则可以直接通过线程句柄,把消息发送到该线程消息队列中。

                                                   

 

用消息机制通信

 

SendMessage是windows api,用来把一个消息发送到一个窗口的消息队列。这个方法是个阻塞方法,也就是操作系统会确保消息的确发送到目的消息队列,并且该消息被处理完毕以后,该函数才返回。返回之前,调用者将会被暂时阻塞。

PostMessage也是一个用来发送消息到窗口消息队列的api函数,但这个方法是非阻塞的。也就是它会马上返回,而不管消息是否真的发送到目的地,也就是调用者不会被阻塞。

2、Invoke and BeginInvoke

 

 

                                                        Invoke or BeginInvoke

 

Invoke或者BeginInvoke方法都需要一个委托对象作为参数。委托类似于回调函数的地址,因此调用者通过这两个方法就可以把需要调用的函数地址封送给界面线程。这些方法里面如果包含了更改控件状态的代码,那么由于最终执行这个方法的是界面线程,从而避免了竞争条件,避免了不可预料的问题。如果其它线程直接操作界面线程所属的控件,那么将会产生竞争条件,造成不可预料的结果。

使用Invoke完成一个委托方法的封送,就类似于使用SendMessage方法来给界面线程发送消息,是一个同步方法。也就是说在Invoke封送的方法被执行完毕前,Invoke方法不会返回,从而调用者线程将被阻塞。

使用BeginInvoke方法封送一个委托方法,类似于使用PostMessage进行通信,这是一个异步方法。也就是该方法封送完毕后马上返回,不会等待委托方法的执行结束,调用者线程将不会被阻塞。但是调用者也可以使用EndInvoke方法或者其它类似WaitHandle机制等待异步操作的完成。

但是在内部实现上,Invoke和BeginInvoke都是用了PostMessage方法,从而避免了SendMessage带来的问题。而Invoke方法的同步阻塞是靠WaitHandle机制来完成的。

3、使用场合问题

如果你的后台线程在更新一个UI控件的状态后不需要等待,而是要继续往下处理,那么你就应该使用BeginInvoke来进行异步处理。

如果你的后台线程需要操作UI控件,并且需要等到该操作执行完毕才能继续执行,那么你就应该使用Invoke。否则,在后台线程和主截面线程共享某些状态数据的情况下,如果不同步调用,而是各自继续执行的话,可能会造成执行序列上的问题,虽然不发生死锁,但是会出现不可预料的显示结果或者数据处理错误。

可以看到ISynchronizeInvoke有一个属性,InvokeRequired。这个属性就是用来在编程的时候确定,一个对象访问UI控件的时候是否需要使用Invoke或者BeginInvoke来进行封送。如果不需要那么就可以直接更新。在调用者对象和UI对象同属一个线程的时候这个属性返回false。在后面的代码分析中我们可以看到,Control类对这一属性的实现就是在判断调用者和控件是否属于同一个线程的。

三、Delegate.BeginInvoke

通过一个委托来进行同步方法的异步调用,也是.net提供的异步调用机制之一。但是Delegate.BeginInvoke方法是从ThreadPool取出一个线程来执行这个方法,以获得异步执行效果的。也就是说,如果采用这种方式提交多个异步委托,那么这些调用的顺序无法得到保证。而且由于是使用线程池里面的线程来完成任务,使用频繁,会对系统的性能造成影响。

Delegate.BeginInvoke也是讲一个委托方法封送到其它线程,从而通过异步机制执行一个方法。调用者线程则可以在完成封送以后去继续它的工作。但是这个方法封送到的最终执行线程是运行库从ThreadPool里面选取的一个线程。

这里需要纠正一个误区,那就是Control类上的异步调用BeginInvoke并没有开辟新的线程完成委托任务,而是让界面控件的所属线程完成委托任务的。看来异步操作就是开辟新线程的说法不一定准确。

 

终于看到了,这是在判断windows窗体线程和当前的调用者线程是否是同一个,如果是同一个就没有必要封送了,直接访问这个GUI控件吧。否则,就不要那么直接表白了,就需要Invoke或者BeginInvoke做媒了。

 

五、C#多线程 Invoke方法的使用 

在多线程编程中,我们经常要在工作线程中去更新界面显示,而在多线程中直接调用界面控件的方法是错误的做法,Invoke 和 BeginInvoke 就是为了解决这个问题而出现的,使你在多线程中安全的更新界面显示。

正确的做法是将工作线程中涉及更新界面的代码封装为一个方法,通过 Invoke 或者 BeginInvoke 去调用,两者的区别就是一个导致工作线程等待,而另外一个则不会。

而所谓的“一面响应操作,一面添加节点”永远只能是相对的,使 UI 线程的负担不至于太大而已,因为界面的正确更新始终要通过 UI 线程去做,我们要做的事情是在工作线程中包揽大部分的运算,而将对纯粹的界面更新放到 UI 线程中去做,这样也就达到了减轻 UI 线程负担的目的了。

再举个简单例子说明下使用方法,比如你在启动一个线程,在线程的方法中想更新窗体中的一个TextBox..

using System.Threading;

         public delegate void MyInvoke(string str);

         private void btnStartThread_Click(object sender, EventArgs e)         {            
            Thread thread = new Thread(new ThreadStart(DoWord));
            thread.Start();        
                             }

     public void DoWord()  { 

           MyInvoke mi = new MyInvoke(SetTxt);

            BeginInvoke(mi,new object[]{"abc"}); 
                       }

        public void SetTxt(string str)
        {
            txtReceive.Text += str + System.Environment.NewLine;
        } 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_33804990/article/details/86046297

智能推荐

Unity使用摇杆触屏控制_unity 把awsd改成安卓按键-程序员宅基地

文章浏览阅读3.2k次,点赞2次,收藏12次。参考来源此例的目标:将一个游戏项目中操控由键盘wasd移动鼠标左键射击,转为摇杆触屏移动,点击button射击。-------------------------------------------------------注意:如果摇杆在某个canvas下记得该canvas用screen space - overlay模式(否则摇杆中心点不会随着屏幕走)。canvas scaler选择..._unity 把awsd改成安卓按键

安装绿色版PostgreSQL及启动服务_pg_ctl.exe 绿色启动-程序员宅基地

文章浏览阅读6.7k次。安装环境windows7 32位 系统postgresql-9.0.4-1-windows-binaries=====之前先是下载的一个postgresql-8.4.2-1-windows版本,这个版本是安装版,在安装的过程中就不断报错,不过也勉强装完了,只是装完之后无法连接数据库,原因应该是win7的事儿,在虚拟机xpsp3安装使用正常,不过又不想每次写代码的时候都开虚拟_pg_ctl.exe 绿色启动

笔记本外接显示器设置全屏壁纸_macbook外接显示器壁纸怎么调整-程序员宅基地

文章浏览阅读9.9k次。方法5:换壁纸(但是有重要关键点!)这个是我的方法,需要一点运气,就是找到一张和外接显示器分辨率一样的壁纸,或者接近的。说白了就是换张壁纸。然后关键点来了一般我们都是双击打开图片,右键设置为桌面背景,但是这里千万别这样,因为笔记本是你的主屏,所以就算你用的壁纸是1920*1200,电脑也会自动裁剪成1366*768。正确的方法是右键——个性化——桌面背景,找到壁纸放在哪个文件夹_macbook外接显示器壁纸怎么调整

Android网格布局实现--GridView_neargridview-程序员宅基地

文章浏览阅读1.3k次。想要实现这个功能,可以使用recyclerview,可以使用GridView。先说下使用GridView实现吧。布局:GridView android:id="@+id/name_search_gridView" android:layout_width="match_parent" android:layout_height="match_parent" _neargridview

mui 引导页 guide.html index.html-程序员宅基地

文章浏览阅读479次。<!DOCTYPE html><html><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scal

SpringBoot整合Swagger2实现在线生成接口文档_swagger 201-程序员宅基地

文章浏览阅读1.6k次。源码获取GitHub:pom.xml文件添加依赖<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version><..._swagger 201

随便推点

Delphi连接AutoCAD_计算任意线条的长度宏的嵌套-程序员宅基地

文章浏览阅读142次。在AutoCAD里可以通过 List 和Area命令求的样条曲线等任意一条曲线的长度。但是要在delphi得到AutoCAD里任意一条曲线的长度就需要编写宏,然后再将宏嵌入到Delphi里这是在AutoCAD2004下调试通过的宏Sub SecFunc() Dim SelectionSet As AcadSelectionSet Dim lSpLine As ..._delphi autocad

数据结构-双向链表(c++)超全超详细_c++双向链表-程序员宅基地

文章浏览阅读1.5w次,点赞33次,收藏231次。文章目录前言一、双向链表是什么?二、双向链表上的基本操作1.定义双向链表2.初始化双链表3.前插法创建双链表4.尾插法创建双链表5.双向链表的遍历输出6.双链表的指定位置插入7.双链表的按位取值8.双链表的任意位置删除9.双链表的销毁三、全部代码(主函数部分比较凌乱)总结前言单链表结点中只有一个指向其后继的指针,使得单链表只能从头结点依次顺序地向后遍历。要访问某个结点的前驱结点(插入,删除操作时),只能从头开始遍历,访问后继结点的时间复杂度为O(1),访问前驱结点的时间复杂度为O(n)。提示:以._c++双向链表

Azure + 5G + AI + IOT可以这么玩_azure 5g private-程序员宅基地

文章浏览阅读425次。欢迎转发扩散,这是对我最大的支持!5G场景下的三大特点分别对应着一些具体的业务场景,eMBB针对高清视频等系列应用;mMTC针对智慧城市等物联网应用;uRLLC针对工业控制或者远程驾驶等应用。在这几类场景中我们举个极端的案例:北京的某个工厂,控制系统在上海,我们在北京和上海之间拉了一根网线进行连接,那么设备执行了某个步骤之后,需要发请求到上海,上海的控制系统是一台奔三的服务器,这套系统显然不能工作对吧?采用何种改进才能正常工作呢?1.控制系统从远程下沉到工厂内部,2.设备和控制系统._azure 5g private

http://blog.csdn.net/liuhe688/article/details/6715983_数据库c.islast()-程序员宅基地

文章浏览阅读367次。上次我向大家介绍了SQLite的基本信息和使用过程,相信朋友们对SQLite已经有所了解了,那今天呢,我就和大家分享一下在Android中如何使用SQLite。现在的主流移动设备像Android、iPhone等都使用SQLite作为复杂数据的存储引擎,在我们为移动设备开发应用程序时,也许就要使用到SQLite来存储我们大量的数据,所以我们就需要掌握移动设备上的SQLite开发技巧。对于An_数据库c.islast()

毛玻璃效果_vba窗体毛玻璃效果-程序员宅基地

文章浏览阅读299次。毛玻璃效果步骤是:1. 创建UIBlurEffect类的实例,并指定某一种毛玻璃效果。2. 创建UIVisualEffectView类的实例,将步骤1中的UIBlurEffect类的实例应用到UIVisualEffectView类的实例上。3. 将UIVisualEffectView类的实例置于待毛玻璃化的视图之上即可。//代码如下:UIImageView _vba窗体毛玻璃效果

Ftp图片上传_csdn ftp上传图片-程序员宅基地

文章浏览阅读597次。创建 FtpConfig 配置类package com.fh.util;public interface FtpConfig { //ftp服务器ip地址 public static final String FTP_ADDRESS = "192.168.230.132"; //端口号 public static final int FTP_PORT = 21; //用户名 public static final String FTP_USERNAME_csdn ftp上传图片