在Invoke或者BeginInvoke的使用中无一例外地使用了委托Delegate,至于委托的本质请参考我的另一随笔:对.net事件的看法。

一、为什么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并没有开辟新的线程完成委托任务,而是让界面控件的所属线程完成委托任务的。看来异步操作就是开辟新线程的说法不一定准确。

四、用Reflector察看一些相关代码

1、Control.BeginInvoke and Control.Invoke

public IAsyncResult BeginInvoke(Delegate method, params object[] args)

{

using (new MultithreadSafeCallScope())

{

return (IAsyncResult) this.FindMarshalingControl().MarshaledInvoke(this, method, args, false);

}

}

public object Invoke(Delegate method, params object[] args)

{

using (new MultithreadSafeCallScope())

{

return this.FindMarshalingControl().MarshaledInvoke(this, method, args, true);

}

}

这里的FindMarshalingControl方法通过一个循环向上回溯,从当前控件开始回溯父控件,直到找到最顶级的父控件,用它作为封送对象。例如,我们调用窗体上一个进度条的Invoke方法封送委托,但是实际上会回溯到主窗体,通过这个控件对象来封送委托。因为主窗体是主线程消息队列相关的,发送给主窗体的消息才能发送到界面主线程消息队列。

我们可以看到Invoke和BeginInvoke方法使用了同样的实现,只是MarshaledInvoke方法的最后一个参数值不一样。

2、MarshaledInvoke

private object MarshaledInvoke(Control caller, Delegate method, object[] args, bool synchronous)

{

int num;

if (!this.IsHandleCreated)

{

throw new InvalidOperationException(SR.GetString("ErrorNoMarshalingThread"));

}

if (((ActiveXImpl) this.Properties.GetObject(PropActiveXImpl)) != null)

{

IntSecurity.UnmanagedCode.Demand();

}

bool flag = false;

if ((SafeNativeMethods.GetWindowThreadProcessId(new HandleRef(this, this.Handle), out num) == SafeNativeMethods.GetCurrentThreadId()) && synchronous)

{

flag = true;

}

ExecutionContext executionContext = null;

if (!flag)

{

executionContext = ExecutionContext.Capture();

}

ThreadMethodEntry entry = new ThreadMethodEntry(caller, method, args, synchronous, executionContext);

lock (this)

{

if (this.threadCallbackList == null)

{

this.threadCallbackList = new Queue();

}

}

lock (this.threadCallbackList)

{

if (threadCallbackMessage == 0)

{

threadCallbackMessage = SafeNativeMethods.RegisterWindowMessage(Application.WindowMessagesVersion + "_ThreadCallbackMessage");

}

this.threadCallbackList.Enqueue(entry);

}

if (flag)

{

this.InvokeMarshaledCallbacks();

}

else

{            //终于找到你了,PostMessage

UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);

}

if (!synchronous) //如果是异步,那么马上返回吧

{

return entry;

}

if (!entry.IsCompleted) //同步调用没结束,阻塞起来等待吧

{

this.WaitForWaitHandle(entry.AsyncWaitHandle);

}

if (entry.exception != null)

{

throw entry.exception;

}

return entry.retVal;

}

怎么样,我们终于看到PostMessage了吧?通过windows消息机制实现了封送。而需要封送的委托方法作为消息的参数进行了传递。关于其它的代码这里不作进一步解释。

3、InvokeRequired

public bool InvokeRequired

{

get

{

using (new MultithreadSafeCallScope())

{

HandleRef ref2;

int num;

if (this.IsHandleCreated)

{

ref2 = new HandleRef(this, this.Handle);

}

else

{

Control wrapper = this.FindMarshalingControl();

if (!wrapper.IsHandleCreated)

{

return false;

}

ref2 = new HandleRef(wrapper, wrapper.Handle);

}

int windowThreadProcessId = SafeNativeMethods.GetWindowThreadProcessId(ref2, out num);

int currentThreadId = SafeNativeMethods.GetCurrentThreadId();

return (windowThreadProcessId != currentThreadId);

}

}

}

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

转载于:https://www.cnblogs.com/C-CHERS/p/4890131.html

为什么Control类提供了Invoke和BeginInvoke机制相关推荐

  1. 委托的Invoke 和 BeginInvoke 与Control的Invoke和BeginInvoke(转-因为写得很好)

    原文地址:http://www.cnblogs.com/worldreason/archive/2008/06/09/1216127.html Invoke and BeginInvoke Invok ...

  2. (转)Invoke and BeginInvoke

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

  3. Invoke and BeginInvoke BeginInvoke和EndInvoke方法 (转)2

    七.其他组件的BeginXXX和EndXXX方法 在其他的.net组件中也有类似BeginInvoke和EndInvoke的方法,如System.Net.HttpWebRequest类的BeginGe ...

  4. C# Invoke和BeginInvoke(1)

    近日,被Control的Invoke和BeginInvoke搞的头大,就查了些相关的资料,整理如下.感谢这篇文章对我的理解Invoke和BeginInvoke的真正含义 . (一)Control的In ...

  5. C#窗体中Invoke和BeginInvoke方法详解

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

  6. Invoke and BeginInvoke

    本文转自:http://www.cnblogs.com/worldreason/archive/2008/06/09/1216127.html 在Invoke或者BeginInvoke的使用中无一例外 ...

  7. Invoke和BeginInvoke的详细理解(C#)

    在项目中,当使用Winform(基于.NET Framework)构建程序的GUI界面时,若以TextBox为列,要在其它线程中更新控件的界面显示,初学者往往是使用: this.textBox.Tex ...

  8. .Net基础——程序集与CIL HttpClient封装方法 .Net Core 编码规范 C#中invoke和beginInvoke的使用 WebServeice 动态代理类...

    .Net基础--程序集与CIL 1. 程序集和CIL: 程序集是由.NET语言的编译器接受源代码文件产生的输出文件,通常分为 exe和dll两类,其中exe包含Main入口方法可以双击执行,dll则需 ...

  9. C# Control的Invoke和BeginInvoke

    之前在项目中遇到了UI界面更新出错的问题,后来在网上找了很多资料,终于解决,先将资料整理如下: 为什么需要Control.Invoke和Control.BeginInvoke?? 如果从另外一个线程操 ...

最新文章

  1. 管理类业务系统菜单部分美化经验分享,把所有好的东西拿过来拼凑并不容易能形成整体的效果...
  2. 在VmWare Workstation 6.5上安装Esx 3.5 U3之二
  3. Autodesk布道GIS新理念
  4. 使用xib封装一个view的步骤
  5. 专访阿里云MVP王俊杰:开发者的超能力是用技术让世界更美好
  6. KeyError: [] not found in axis_高调又有质感,女星最爱的至IN单品原来是它!
  7. delphi精品项目源码_项目是如何死掉的?太过真实!
  8. 25个必须记住的SSH命令
  9. jmeter对乱码如何处理_JMeter读取 Excel 表中用例数据实现接口压测
  10. 神舟笔记本电源管理软件_笔记本电脑长期不用充不上电了?原来问题就出在这儿...
  11. MyEclipse 为xml添加本地的dtd文件
  12. 百万 Android 用户受感染!
  13. 基于开源文本摘要模块sumy的文本摘要生成实践
  14. 基础才是重中之重~对象的生与死
  15. 修改 oracle 数据库 TNSLSNR.exe 占用 8080 端口
  16. JAVA深拷贝与浅拷贝(呕心沥血之作)
  17. FatFs- 通用FAT文件系统模块
  18. 交通灯—VHDL设计
  19. 1、 域名系统的主要功能是什么?互联网的域名结构是怎样的?域名系统中的本地域名服务器、根域名服务器、顶级域名服务器以及权限域名服务器有何区别?2、 假定要从已知的URL获得一个万维网文档。若该万维网服
  20. 天猫精灵使用体验之二——家用电器的智能化改造(借助天猫精灵实现家用电器的语音控制)

热门文章

  1. 复习一个知识点——原、反、补码以及取反操作
  2. 在路上---学习篇(一)Python 数据结构和算法 (4) --希尔排序、归并排序
  3. 一般源码安装添加的GD库 是不支持 jpeg 格式的图片的
  4. 基于WordNet词典的本体源
  5. 没事试试50mm1.4
  6. 微软Java面试题-按照字母排序
  7. 集合框架源码学习之HashMap(JDK1.8)
  8. 对GC垃圾收集的一点整理
  9. windows cmd执行git log命令中文显示乱码
  10. 混迹于IT纯屌界中独一无二的丸子