问题的产生:

  我的WinForm程序中有一个用于更新主窗口的工作线程(worker thread),但文档中却提示我不能在多线程中调用这个form(为什么?),而事实上我在调用时程序常常会崩掉。请问如何从多线程中调用form中的方法呢?

  解答:

  每一个从Control类中派生出来的WinForm类(包括Control类)都是依靠底层Windows消息和一个消息泵循环(message pump loop)来执行的。消息循环都必须有一个相对应的线程,因为发送到一个window的消息实际上只会被发送到创建该window的线程中去。其结果是,即使提供了同步(synchronization),你也无法从多线程中调用这些处理消息的方法。大多数plumbing是掩藏起来的,因为WinForm是用代理(delegate)将消息绑定到事件处理方法中的。WinForm将Windows消息转换为一个基于代理的事件,但你还是必须注意,由于最初消息循环的缘故,只有创建该form的线程才能调用其事件处理方法。如果你在你自己的线程中调用这些方法,则它们会在该线程中处理事件,而不是在指定的线程中进行处理。你可以从任何线程中调用任何不属于消息处理的方法。

  Control类(及其派生类)实现了一个定义在System.ComponentModel命名空间下的接口 -- ISynchronizeInvoke,并以此来处理多线程中调用消息处理方法的问题:

public interface ISynchronizeInvoke
{
 object Invoke(Delegate method,object[] args);
 IAsyncResult BeginInvoke(Delegate method,object[] args);
 object EndInvoke(IAsyncResult result);
 bool InvokeRequired {get;}
}

  ISynchronizeInvoke提供了一个普通的标准机制用于在其他线程的对象中进行方法调用。例如,如果一个对象实现了ISynchronizeInvoke,那么在线程T1上的客户端可以在该对象中调用ISynchronizeInvoke的Invoke()方法。Invoke()方法的实现会阻塞(block)该线程的调用,它将调用打包发送(marshal)到 T2,并在T2中执行调用,再将返回值发送会T1,然后返回到T1的客户端。Invoke()方法以一个代理来定位该方法在T2中的调用,并以一个普通的对象数组做为其参数。

  调用者还可以检查InvokeRequired属性,因为你既可以在同一线程中调用ISynchronizeInvoke也可以将它重新定位(redirect)到其他线程中去。如果InvokeRequired的返回值是false的话,则调用者可以直接调用该对象的方法。

  比如,假设你想要从另一个线程中调用某个form中的Close方法,那么你可以使用预先定义好的的MethodInvoker代理,并调用Invoke方法:

Form form;
/* obtain a reference to the form, 
then: */
ISynchronizeInvoke synchronizer;
synchronizer = form;

if(synchronizer.InvokeRequired)
{
MethodInvoker invoker = new 
MethodInvoker(form.Close);
synchronizer.Invoke(invoker,null);
}
else
form.Close();

  ISynchronizeInvoke不仅仅用于WinForm中。例如,一个Calculator类提供了将两个数字相加的Add()方法,它就是通过ISynchronizeInvoke来实现的。用户必须确定ISynchronizeInvoke.Invoke()方法的调用是执行在正确的线程中的。

  C# 在正确的线程中写入调用

  列表A. Calculator类的Add()方法用于将两个数字相加。如果用户直接调用Add()方法,它会在该用户的线程中执行调用,而用户可以通过ISynchronizeInvoke.Invoke()将调用写入正确的线程中。

  列表A:

public class Calculator : ISynchronizeInvoke
{
 public int Add(int arg1,int arg2)
 { 
  int threadID = Thread.CurrentThread.GetHashCode();
  Trace.WriteLine( "Calculator thread ID is " + threadID.ToString());
  return arg1 + arg2;
 }
 //ISynchronizeInvoke implementation 
 public object Invoke(Delegate method,object[] args)
 {
  public IAsyncResult BeginInvoke(Delegate method,object[] args)
  {
   public object EndInvoke(IAsyncResult result)
   {
    public bool InvokeRequired
    {
    }
   }
   //Client-side code
   public delegate int AddDelegate(int arg1,int arg2);

    int threadID = Thread.CurrentThread.GetHashCode();
    Trace.WriteLine("Client thread ID is " + threadID.ToString());

    Calculator calc;
    /* Some code to initialize calc */

    AddDelegate addDelegate = new AddDelegate(calc.Add);

    object[] arr = new object[2];
    arr[0] = 3;
    arr[1] = 4;

    int sum = 0;
    sum = (int) calc.Invoke(addDelegate,arr);
    Debug.Assert(sum ==7);

    /* Possible output:
    Calculator thread ID is 29
    Client thread ID is 30 
    */

  或许你并不想进行同步调用,因为它被打包发送到另一个线程中去了。你可以通过BeginInvoke()和EndInvoke()方法来实现它。你可以依照通用的.NET非同步编程模式(asynchronous programming model)来使用这些方法:用BeginInvoke()来发送调用,用EndInvoke()来实现等待或用于在完成时进行提示以及收集返回结果。

  还值得一提的是ISynchronizeInvoke方法并非安全类型。 类型不符会导致在执行时被抛出异常,而不是编译错误。所以在使用ISynchronizeInvoke时要格外注意,因为编辑器无法检查出执行错误。

  实现ISynchronizeInvoke要求你使用一个代理来在后期绑定(late binding)中动态地调用方法。每一种代理类型均提供DynamicInvoke()方法: public object DynamicInvoke(object[] 
args);

  理论上来说,你必须将一个方法代理放到一个需要提供对象运行的真实的线程中去,并使Invoke() 和BeginInvoke()方法中的代理中调用DynamicInvoke()方法。ISynchronizeInvoke的实现是一个非同一般的编程技巧,本文附带的源文件中包含了一个名为Synchronizer的帮助类(helper class)和一个测试程序,这个测试程序是用来论证列表A中的Calculator类是如何用Synchronizer类来实现ISynchronizeInvoke的。Synchronizer是ISynchronizeInvoke的一个普通实现,你可以使用它的派生类或者将其本身作为一个对象来使用,并将ISynchronizeInvoke实现指派给它。

  用来实现Synchronizer的一个重要元素是使用一个名为WorkerThread的嵌套类(nested class)。WorkerThread中有一个工作项目(work item)查询。WorkItem类中包含方法代理和参数。Invoke()和BeginInvoke()用来将一个工作项目实例加入到查询里。WorkerThread新建一个.NET worker线程,它负责监测工作项目的查询任务。查询到项目之后,worker会读取它们,然后调用DynamicInvoke()方法。

add useful link:

How to update the GUI from another thread in C#?

http://stackoverflow.com/questions/661561/how-to-update-the-gui-from-another-thread-in-c

[C#学习]在多线程中如何调用Winform[转]的更多相关文章

  1. [Winform-WebBrowser]-在html页面中js调用winForm类方法

    在winform项目中嵌入了网页,想通过html页面调用后台方法,如何实现呢?其实很简单,主要有三部: 1.在被调用方法类上加上[ComVisible(true)]标签,意思就是当前类可以com组件的 ...

  2. C# winForm webBrowser页面中js调用winForm类方法(转)

      有时我们在winform项目中嵌入了网页,想通过html页面调用后台方法,如何实现呢?其实很简单,主要有三部:   1.在被调用方法类上加上[ComVisible(true)]标签,意思就是当前类 ...

  3. iOS多线程中,队列和执行的排列组合结果分析

    本文是对以往学习的多线程中知识点的一个整理. 多线程中的队列有:串行队列,并发队列,全局队列,主队列. 执行的方法有:同步执行和异步执行.那么两两一组合会有哪些注意事项呢? 如果不是在董铂然博客园看到 ...

  4. 多线程中操作UI

    遇到过要在工作线程中去更新UI以让用户知道进度,而在多线程中直接调用UI控件操作是错误的做法. 最后解决方法是将操作UI的代码封装,通过Invoke / BeginInvoke 去委托调用. 代码封装 ...

  5. 如何在多线程中调用winform窗体控件

    由于 Windows 窗体控件本质上不是线程安全的.因此如果有两个或多个线程适度操作某一控件的状态(set value),则可能会迫使该控件进入一种不一致的状态.还可能出现其他与线程相关的 bug,包 ...

  6. 谨慎使用多线程中的fork 学习!!!!

    前言 在单核时代,大家所编写的程序都是单进程/单线程程序.随着计算机硬件技术的发展,进入了多核时代后,为了降低响应时间,重复充分利用多核cpu的资源,使用多进程编程的手段逐渐被人们接受和掌握.然而因为 ...

  7. 在WPF中调用Winform控件

    最近在项目中用到了人脸识别和指纹识别,需要调用外部设备和接口,这里就用到了在WPF中调用Winform控件. 第一步,添加程序集引用.System.Windows.Forms和WindowsForms ...

  8. [Android学习笔记]Android中多线程开发的一些概念

    线程安全: 在多线程的情况下,不会因为线程之间的操作而导致数据错误. 线程同步: 同一个资源,可能在同一时间被多个线程操作,这样会导致数据错误.这是一个现象,也是一个问题,而研究如何解决此类问题的相关 ...

  9. MacOS和iOS开发中异步调用与多线程的区别

    很多童鞋可能对Apple开发中的异步调用和多线程的区别不是太清楚,这里本猫将用一些简单的示例来展示一下它们到底直观上有神马不同. 首先异步调用可以在同一个线程中,也可以在多个不同的线程中.每个线程都有 ...

随机推荐

  1. 基于TCP协议的客户端

    基于TCP协议的客户端 此客户端能用于下一篇博客的单线程服务器和多线程服务器 import java.io.BufferedReader; import java.io.IOException; im ...

  2. .net 码农转战 iOS - 初探

    好久没写博客了,之前还打算把毕业设计中涉及到的两个算法拿出来说说(脸型分析 + 声音分析),博文都写了一半了,后来实在太忙了,那篇随笔也就沉在草稿列表中没动过. 我原先是专职 .net 开发的,在公司 ...

  3. HTTPS、SSL与数字证书介绍

    在互联网安全通信方式上,目前用的最多的就是https配合ssl和数字证书来保证传输和认证安全了.本文追本溯源围绕这个模式谈一谈. 名词解释 HTTPS:在HTTP(超文本传输协议)基础上提出的一种安全 ...

  4. cygwin下安装hadoop0.20

    安装时选择软件包: editor-两个VIM.base-SED.Net-OPENSSH.OPENSSL.libs-libintl3和libintl8 环境变量: path=%JAVA_HOME%\bi ...

  5. javascript如何监听页面刷新和页面关闭事件

    本文转之http://www.qqtimezone.top 在我们的日常生活中,时常遇到这么一种情况,当我们在点击一个链接.关闭页面.表单提交时等情况,会提示我们是否确认该操作等信息. 这里就给大家讲 ...

  6. PHP面试题三

    1.nginx使用哪种网络协议? nginx是应用层 我觉得从下往上的话 传输层用的是tcp/ip 应用层用的是http fastcgi负责调度进程 2. <? echo 'hello tush ...

  7. notepad++下载Subversion插件,显示intalltion of subversion failed

    notepad++安卓subversion的插件不成功,是因为我们下载TortoiseSVN客户端的版本跟subversion的版本不兼容 一.背景: 在新浪云平台上开发微信公众账号,因为要使用SVN ...

  8. 基于RMAN从活动数据库异机克隆(rman duplicate from active DB)

    Oracle 11g RMAN能够实现基于活动数据库进行异机克隆,从而省去需要先备份再ftp到辅助服务器的过程.这一切可以全部交给Oracle来搞定.在克隆期间,Oracle会读取Target DB的 ...

  9. 应用程序无法正常启动0xc0150002 解决方式

        我也遇到过此问题,解决的方法: 方案一: 在项目的"属性|配置属性|链接器|常规"中的"启用增量链接"选择"否".此方法阻断了问题产 ...

  10. msvcp110.dll丢失

    方法1:建议下载并安装[百度电脑专家],在搜索框内输入“vs2012运行时库缺失”,在搜索结果里面选择[立即修复],修复完成后验证是否正常: 方法2:手动修复 2.1 在[百度]下载“msvcp110 ...