一、为什么 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机制来完成的。

先来看看Send与Post这两个英文单词的意思:Send有发送的意思,而Post具有投寄的意思。
      Send:  相当于邮寄员,他会将快件亲手交给收件人,并且需要收件人签字,他才闪人。而在消息机制中,就是说,系统(邮寄员)会将收到的消息(邮局分发)直接发送到某个窗口的窗口过程(收件人),并且需要该窗口作出处理(收件人签字)才返回。 这东东就是SendMessage
      Post:  相当于邮局、邮筒等等,我们写好信好,会将信交给邮局,或投寄到邮筒里,而什么时候发送,发送到哪里都由邮局来处理,我们投寄信件的时候,是不会等候这封信件到达收件人手里,然后才回家的。 而在消息机制中,就是说,系统(我们)将收到的消息(信件)投寄到应用程序的消息循环(相当于邮筒)中,然后就闪人,具体啥时候处理这条消息(啥时候发送邮件),那就得看“办事效率”了。
      区别很明显,SendMessage的消息是不进队列的,而PostMessage的需要排队。 
      但,值得说明的是:虽然一个要进队,一个不进队,但是最终处理消息的地方都一样:都是系统调用窗口过程进行处理(收件人作出反应)
      
    PreTranslateMessage
     我们可以PreTranslateMessage来对消息预处理,该用的用,不该用的不用。那么,是否Send和Post来的消息我们都可以用它来预处理呢?
     答案当然是否定的:如果你在深圳,要写封信寄到东北去,那么投寄方式有两种:1、自己带着信跑到东北去,2、将信放到邮局,由邮局分发。
     第1 种情况下,就是SendMessage了,这个时候,还有谁能预处理你的信呢?
     第2种情况下,就是PostMessage了,这个时候,嘿嘿,万一运气不好,被邮局给用PreTranslateMessage给截了.....
     所以说,用SendMessage发送的消息是不能用PreTranslateMessage来预处理的。而Post的就可以,因为它要经过“第三者”。

3、使用场合问题

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

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

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

三、 Delegate.BeginInvoke

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

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

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

Invoke与BeginInvoke的更多相关文章

  1. C# Invoke或者BeginInvoke的使用

    在Invoke或者BeginInvoke的使用中无一例外地使用了委托Delegate. 一.为什么Control类提供了Invoke和BeginInvoke机制? 关于这个问题的最主要的原因已经是do ...

  2. C#中Invoke 和 BeginInvoke 的区别

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

  3. 控件的invoke和beginInvoke方法

    System.Windows.Forms.Timer 的timer是在主线程上执行的,因此在timer的tick事件中操作界面上的控件不会发生线程的安全性检测. Control的invoke和begi ...

  4. C#的Invoke和BeginInvoke

    在Invoke或者BeginInvoke的使用中无一例外地使用了委托Delegate,至于委托的本质请参考我的另一随笔:对.net事件的看法. 一.为什么Control类提供了Invoke和Begin ...

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

    今天关闭一个窗体,报出这样的一个错误"在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke.",这个不用多想,肯定是那个地方没有释放掉.既然碰到这个问题, ...

  6. 安装SQL Server出现在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke的错误解决办法

    以下是错误报告:   标题: SQL Server 安装程序失败. ------------------------------   SQL Server 安装程序遇到以下错误:   在创建窗口句柄之 ...

  7. invoke和beginInvoke

    首先说下,invoke和begininvoke的使用有两种情况: 1. control中的invoke.begininvoke. 2. delegrate中的invoke.begininvoke. 这 ...

  8. [整][转]Invoke和BeginInvoke的使用

    在Invoke或者BeginInvoke的使用中无一例外地使用了委托Delegate. 一.为什么Control类提供了Invoke和BeginInvoke机制? 关于这个问题的最主要的原因已经是do ...

  9. c#多线程(UI线程,控件显示更新) Invoke和BeginInvoke 区别

    如果只是直接使用子线程访问UI控件,直接看内容三,如果想深入了解从内容一看起. 一.Control.Invoke和BeginInvoke方法的区别 先上总结: Control.Invoke 方法 (D ...

  10. Dispatcher中Invoke与BeginInvoke

    [同步]Invoke Application.Current.Dispatcher.Invoke(AutoIncreaseNumber); [异步]BeginInvoke Application.Cu ...

随机推荐

  1. 正确处理WPF中Slider值改变事件的方式

    最近在用WPF数据绑定重写一下播放器项目时遇到的关于Slider的问题,在窗体透明度调节和播放进度调节上用了Slider控件.调节窗体透明度我是 这么想的:将窗体的Opacity属性的值与Slider ...

  2. php新手:XAMMP打开开源php代码

    1.启动XAMPP 打开XAMPP启动 Apache 和 MySql 如果发现默认的80端口被IIS占用了 请参考 这个  如何改变apache被占用的端口 2.将源代码复制到 磁盘(XAMPP安装目 ...

  3. MySQL 从库日志比主库多

    在群里聊天的时候,一个群友说,生产库主库宕机,但是主从数据库数据一致,但是从库的日志比主库多,很是不理解! 咨询后发现,生产库的主库没有设置sync_binlog=1,而是为sync_binlog=0 ...

  4. [转]宏的高级使用--##,__VA_ARGS__, __FILE__, __FUNCTION__等

    [转]宏的高级使用--##,__VA_ARGS__, __FILE__, __FUNCTION__等 http://blog.csdn.net/yiya1989/article/details/784 ...

  5. C/C++ 内联函数

    内联函数具备一般函数的性质,但是不需要调用,而是在编译阶段,会用函数体替换函数名被调用的地方.可以节省调用时间(进出栈.保存上下文). 在编译层面和宏的作用相同.内联函数的展开在编译阶段,宏展开在预处 ...

  6. 修改info

      新增Key: NSLocationAlwaysUsageDescription 和 NSLocationWhenInUseUsageDescription ,这两个Key的值将分别用于描述应用程序 ...

  7. java使用.net的webservice

    1.下载最新的axis2 http://mirrors.hust.edu.cn/apache//axis/axis2/java/core/1.6.3/axis2-1.6.3-bin.zip 2.解压使 ...

  8. MITK Tutorial

      MITK 设计来使用模块化和高度的代码重用,既能作为 pure software library或complete application framework.它的结构概览图如下: ITK:提供分 ...

  9. 2877: [Noi2012]魔幻棋盘 - BZOJ

    DescriptionInput 第一行为两个正整数N,M,表示棋盘的大小. 第二行为两个正整数X,Y,表示棋盘守护者的位置. 第三行仅有一个正整数T,表示棋盘守护者将进行次操作. 接下来N行,每行有 ...

  10. 【POJ】【3710】Christmas Game

    博弈论 贾志豪论文上的题目……题解请看论文 Orz了一下Hzwer Source Code Problem: User: sdfzyhy Memory: 716K Time: 0MS Language ...