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

this.textBox.Text = "10";

当这样使用时,在程序员运行后可能会使程序触发“不能从不是创建该控件的线程中调用它”的异常信息。造成这种异常的原因在于,窗体控件是在主线程中创建的(比如this.Controls.Add(控件的组件对象)),当更新控件的界面显示时,是在其它线程,并不是在主线程中进行。当在其它线程中更新控件的显示界面时,可能会与主线程发生线程冲突。若此时主线程正在重绘此控件,而其它线程也在更新此控件的显示界面,其它线程就会和主线程进行竞争,造成画面混乱,甚至可能会出现死锁。
因此在Windows GUI编程中有一个规则,只能通过创建控件的线程来操作控件的数据,否则会产生不可预料的后果。
因此在.NET中,为了更好的解决这些问题,通过Control类实现了ISynchronizeInvoke接口,提供了InvokeBeginInvoke方法,提供让其它线程对GUI界面的操作。ISynchronizeInvoke接口详细信息如下所示:

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; }
}

如果从创建控件的线程外操作GUI控件,就需要使用ISynchronizeInvoke接口中的Invoke(同步执行)和BeginInvoke(异步执行)方法,通过一个委托把调用封送到控件所属的线程上执行。

通过上面的介绍,我们不得不思考一个问题,为什么Control类提供了InvokeBeginInvoke方法?
关于这个问题,我主要从以下几个方面来进行讲解:
1.Windows程序消息机制
Windows中,GUI程序是基于消息机制,有一个主线程维护着一个消息泵。这个主线程维护着整个窗体以及窗体上面的子控件。当它获取到消息时,就会调用DispatchMessage()方法派遣消息,会对窗体上的窗口过程进行调用(窗口过程里面由程序员提供的窗体数据更新代码以及其它的代码)。

Windows GUI程序的消息循环
GUI窗体上的所有消息是通过Windows程序的消息队列所提供。在GUI程序运行时,为了不断的从队列中获取消息,Windows程序在while循环中主要使用GetMessage()方法,这是个阻塞方法,当Windows消息队列为空时,此方法就会被阻塞,此时while循环就会停止(主要避免程序把CPU耗尽,使其它程序难以得到响应。若需要CPU进行最大限度的运行(如3D游戏),一般使用PeekMessage()方法,它不会被Windows阻塞,从而保证程序的流畅和比较高的帧速)。

  1. .NET的消息循环
public static void Main(string[] args)
{Form frm = new Form();Application.Run(frm);
}

在上述代码中,.NET窗体封装了while循环,Application.Run(frm)方法开启循环过程。

3.消息机制—进程与线程间的通信
Windows消息发送
Windows消息机制是Windows平台上的线程或者进程间通信机制之一。Windows消息值的定义为一个数据结构,其中最重要的是消息类型,为一个整数;然后是消息参数,消息参数可以表达很多。
当然,Windows也提供了一些API用来向一个线程队列发送消息。因此,一个线程可以向另一个线程的消息队列发送消息,这样就实现了线程间的通信。有些API发送消息需要一个窗口句柄,这种函数可以把消息发送到指定窗口的主线程消息队列,而有些可直接通过线程句柄,把消息发送到该线程的消息队列中。

消息机制通信
Windows API中提供了SendMessage()方法,把一个消息发送到一个窗口的消息队列。这个方法为阻塞方法,操作系统会确保消息确实发送到目的消息队列,并且该消息被处理后,该方法才返回,并将控制权返还给调用者,在返回之前,调用者将暂时被阻塞。
PostMessage()方法也是发送消息到窗口的消息队列,由Windows API所提供,但这个方法是非阻塞,调用者在调用该方法后,会立即返回,操作系统不会确保消息是否的确发送到目的消息队列中,调用者也不会被阻塞。

InvokeBeginInvoke方法
由上述可知,InvokeBeginInvoke方法由ISynchronizeInvoke接口提供,且都需要一个委托对象作为参数。委托类似于回调函数的地址,调用者可通过这两个方法将需要调用的函数地址封送到界面线程。如果这些封送给界面线程的方法包含了更改界面控件的代码,由于最终执行这个方法是在界面控件线程中,从而避免了线程之间的竞争。
使用Invoke方法完成委托的封送,类似于使用SendMessage方法给界面线程发送消息,为同步方法。在Invoke封装的方法未执行完毕之前,Invoke方法是不会返回,使调用者线程被阻塞。
使用BeginInvoke方法完成委托的封送,类似于使用PostMessage方法给界面线程发送消息,为异步方法。在完成委托的封送后,BeginInvoke方法立即返回,不会等待委托的方法执行完毕,调用者线程将不会阻塞。但是调用者可以使用EndInvoke方法或者其它类似WaitHandle机制等待异步操作的完成。
但是,InvokeBeginInvoke在内部的实现上都是使用PostMessage方法,从而避免SendMessage方法带来的问题。其中Invoke方法的同步阻塞是使用WaitHandle机制完成。

使用场合
若你的后台线程在更新UI界面且不需要等待,则应该使用BeginInvoke方法进行异步操作。
若你的后台线程在更新UI界面并且需要等待,则应该使用Invoke方法进行同步操作。

ISynchronizeInvoke接口中,可以看到还有一个InvokeRequired。这个属性的作用是:在编程时确定一个对象在访问UI控件时是否需要使用InvokeBeginInvoke方法进行封送,如果不需要则可以直接进行更新。在调用者对象和UI对象属于同一个线程时,该属性返回false。Control类对这一属性的实现是在判断调用者和控件是否同属于一个线程。

Delegate.BeginInvoke
通过一个委托来进行同步方法的异步调用,也是.NET提供的异步调用机制之一。但是Delegate.BeginInvoke方法是从ThreadPool中取出的一个线程来执行这个方法,以获得异步的执行效果。如果采用这种方式提交多个异步委托,这些调用的顺序无法得到保障。而且由于使用的是线程池中的线程来完成任务,若频繁的使用,会对系统的性能造成影响。
Control类中的BeginInvoke方法没有开辟新的线程完成委托任务,而是让界面所属的控件线程完成委托任务(异步操作就是开辟新的线程的说法也不一定准确)。

Control.BeginInvokeControl.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);}
}

从上述代码可以看到InvokeBeginInvoke方法使用了同样的实现,只是MarshaledInvoke方法的最后一个参数值不一样,一个为true,另一个为false
这里的FindMarshalingControl方法通过一个循环向上回溯,从当前控件开始回溯父控件,直到找到最顶级的父控件,用它作为封送对象。例如,我们调用窗体上一个TextBox控件的Invoke方法封送委托,但是实际上会回溯到主窗体,通过这个控件对象来封送委托。因为主窗体是主线程消息队列相关的,发送给主窗体的消息才能发送到界面主线程消息队列。

Invoke和BeginInvoke的详细理解(C#)相关推荐

  1. 我理解的invoke和begininvoke

    我理解的invoke和begininvoke 一直对invoke和begininvoke的使用和概念比较混乱,这两天看了些资料,对这两个的用法和原理有了些新的认识和理解. 首先说下,invoke和be ...

  2. .NET 线程 Thread 进程 Process、线程池 pool、Invoke、begininvoke、异步回调、APM、EAP、TPL、aysnc、await

    windows系统是一个多线程的操作系统.一个程序至少有一个进程,一个进程至少有一个线程.进程是线程的容器,一个C#客户端程序开始于一个单独的线程,CLR(公共语言运行库)为该进程创建了一个线程,该线 ...

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

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

  4. c# Invoke和BeginInvoke 区别详解

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

  5. C#中Invoke 和 BeginInvoke的涵义和区别

    BeginInvoke 方法真的是新开一个线程进行异步调用吗? 参考以下代码: public delegate void treeinvoke(); private void UpdateTreeVi ...

  6. 【分析】浅谈C#中Control的Invoke与BeginInvoke在主副线程中的执行顺序和区别(SamWang)

    今天无意中看到有关Invoke和BeginInvoke的一些资料,不太清楚它们之间的区别.所以花了点时间研究了下. 据msdn中介绍,它们最大的区别就是BeginInvoke属于异步执行的. Cont ...

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

    今天无意中看到有关Invoke和BeginInvoke的一些资料,不太清楚它们之间的区别.所以花了点时间研究了下. 据msdn中介绍,它们最大的区别就是BeginInvoke属于异步执行的. Cont ...

  8. C#:invoke 与 BeginInvoke使用区别

    invoke和begininvoke 区别 一直对invoke和begininvoke的使用和概念比较混乱,这两天看了些资料,对这两个的用法和原理有了些新的认识和理解. 首先说下,invoke和beg ...

  9. C#中Invoke 和 BeginInvoke 的区别

    Invoke 和 BeginInvoke 的真正涵义 .在多线程中如何调用Winform Control.Invoke 方法 (Delegate) :在拥有此控件的基础窗口句柄的线程上执行指定的委托. ...

最新文章

  1. 条理清晰的搭建SSH环境之添加所需jar包
  2. 【转】使用python3的typing模块提高代码健壮性
  3. WinAPI——Windows 消息
  4. C#正则表达式判断输入日期格式是否正确
  5. ROS-WIKI——编写简单的发布者和订阅者(C++和Python版本)
  6. java面向对象特性_java面向对象编程三大特性
  7. Linux的链接工具 putty 以及一些命令。
  8. c#连接kafka_c#操作kafka(上)搭建kafka环境
  9. Windows下安装最新的Apache+PHP+MySQL方法--记录方便自己参考
  10. springboot - 应用实践(N)使用springboot内置的@Scheduled
  11. 实用的BeanUtils工具类
  12. linux 清除终端记录,清除Linux终端命令的历史记录
  13. 呆萝卜 竞品分析报告
  14. 计算机信息与科学专业好吗,俄亥俄州立大学 计算机信息与科学这个专业怎么样...
  15. medusa详细教程
  16. Mysql - 开发技巧(二)
  17. Java毕设_装修公司业务流程管理系统的设计与实现
  18. 渲染多层材料的综合框架
  19. Python语言程序设计基础 第二版(嵩天著)课后答案第四章
  20. STM32——时钟系统RCC详细介绍

热门文章

  1. JSON.parse
  2. Docker command line 学习笔记
  3. 【数电试题】西电通卓模拟卷三
  4. linux系统第一篇(Linux系统入门介绍)
  5. try和catch的用法
  6. 网友质问微软客服:为啥不黑我?!(爆笑)
  7. 【LeetCode】1609. 奇偶树、1122. 数组的相对排序
  8. 如何使用Tableau分析敏捷,开发和网站指标
  9. python爬虫基础小案例, scrapy框架,思路和经验你全都有。
  10. 复旦非全日制研究生计算机,2018年复旦大学信息科学与工程学院非全日制招生计划...