[C#学习]在多线程中如何调用Winform

2016-02-19 17:07 22 1 收藏

下面请跟着图老师小编一起来了解下[C#学习]在多线程中如何调用Winform,精心挑选的内容希望大家喜欢,不要忘记点个赞哦!

【 tulaoshi.com - 编程语言 】

  问题的产生:

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

  解答:

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

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com/bianchengyuyan/)

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

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com/bianchengyuyan/)

  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 = newMethodInvoker(form.Close);synchronizer.Invoke(invoker,null);}elseform.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()方法。

来源:https://www.tulaoshi.com/n/20160219/1614211.html

延伸阅读
我们在写Remoting程序或者其他的一些应用程序的时候难免要和线程打交道,.Net使我们很容易就可以创建一个线程,但是它提供的创建线程和启动线程的方法没有明显的提供参数,假如我们要用线程来启动类里面一个带参数的方法该怎么办?下面就简单的介绍如何使用.NET提供的丰富的框架来实现这个功能。为了可以生动详细的介绍整个过程,我建立下面的一...
介绍 API(Application Programming Interface),我想大家不会陌生,它是我们Windows编程的常客,虽然基于.Net平台的C#有了强大的类库,但是,我们还是不能否认API在Windows编程中的重要性。大多数的编程语言都支持API编程,而.Net平台中的MFC(Microsoft Foundation Class Library)构架本身就封装了大部分的API。 做为程序员,...
前面说过,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生。 C#提供了一个关键字lock,它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,...
与所有的UI开发平台一样,.NET下线程开发图形界面同样要遵循一个基本原则:就是对UI对象的操作一定要在产生该UI对象的线程里进行(该线程称作UI线程),因为大部分UI对象都不是线程安全的。 在.NET中,把调用调用放在UI线程里执行是通过Form类及其子类的Invoke()方法实现的(具体的过程请参考其他资料),可以这样做是因为Form对象保存了创建...
import java.io.*;//多线程编程public class MultiThread{public static void main(String args[]){System.out.println("我是主线程!");//下面创建线程实例thread1ThreadUseExtends thread1=new ThreadUseExtends();//创建thread2时以实现了Runnable接口的THhreadUseRunnable类实例为参数Thread thread2=new Thread(new ThreadU...

经验教程

857

收藏

84
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部