`
ajuanlong
  • 浏览: 107812 次
社区版块
存档分类
最新评论

在多线程中如何调用Winform

 
阅读更多
问题的产生:

  我的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()方法。

分享到:
评论

相关推荐

    WinForm C# 多线程编程并更新界面(UI)

    下面我们来讨论如何在 WinForm 中使用 C# 实现多线程编程并更新界面(UI)。 多线程编程 多线程编程是一种使应用程序可以同时执行多个任务的技术。它可以提高应用程序的响应速度和用户体验。在 WinForm 中,我们...

    简单C#winform多线程委托调用进度条

    在C# WinForm应用开发中,多线程和进度条的使用是常见的需求,尤其是在执行耗时操作如文件上传、下载或大数据处理时,为了不阻塞用户界面(UI),我们通常会创建一个后台线程来执行这些任务,同时通过进度条实时反馈...

    关于winform使用timer进行多线程操作的例子

    总结一下,这个例子展示了如何在Winform应用中利用`System.Timers.Timer`组件进行多线程操作,以及如何处理UI线程与后台线程之间的交互。通过这个例子,开发者可以学习到如何在不影响用户体验的情况下执行定时任务,...

    C#多线程调用Winform窗体[文].pdf

    标题《C#多线程调用Winform窗体》涉及的是C#编程语言中多线程环境下对Winform窗体进行操作的技术点。Winform即Windows窗体应用程序,是C#常用的一种桌面应用程序开发方式。由于GUI窗体通常不是线程安全的,所以在多...

    winform多线程经典实例超级简单易懂

    winform多线程经典实例超级简单易懂。和传统的委托不同。使用了SynchronizationContext技术。具体SynchronizationContext技术优势可以百度。 几行代码就可以实现多线程委托。超级简单。

    winform 多线程处理数据

    本篇将详细讨论如何在WinForm应用中运用多线程处理数据以及异步更新UI。 1. **什么是多线程?** 在计算机科学中,多线程是指在一个进程中同时执行多个不同的代码段,每个代码段称为一个线程。通过多线程,程序可以...

    winform多线程计算调用js

    以下将详细介绍如何在Winform应用中实现多线程调用JavaScript。 首先,了解Winform中的WebBrowser控件。WebBrowser控件是一个内置的组件,它提供了与Web页面交互的能力,包括执行JavaScript代码。通过`WebBrowser....

    C# Winform 多线程下载

    为了在Winform中实现多线程下载,我们需要创建一个窗体(Form)并在其中添加必要的控件,如按钮、进度条等,用于用户交互和显示下载状态。同时,我们需要创建一个后台线程来进行实际的文件下载操作,以避免阻塞UI...

    C# winform动态创建和关闭多线程源码 可运行

    在Winform中,直接在非UI线程中修改UI控件是不允许的,会抛出CrossThreadException异常。为了安全地更新UI,可以使用Control类的Invoke或BeginInvoke方法。它们会在UI线程中调度一个委托,从而安全地修改控件: ```...

    winform 实现多线程登录

    在WinForm中,我们可以使用`ProgressBar`控件来显示登录过程的进度。在新线程中,我们需要定期更新进度条的值,反映登录过程的进展。这通常通过调用`Invoke`方法来在UI线程上安全地修改控件属性。例如: ```csharp ...

    在多线程中如何调用Winform.pdf

    在多线程环境中调用Winform控件的方法是一个复杂的过程,因为Winform控件并不是线程安全的。Windows Forms应用程序中,所有的控件默认都在创建它们的线程(主线程)上运行。如果尝试从另一个线程直接调用控件的方法...

    多线程定时器Web和Winform双版本

    下面将详细介绍多线程定时器的概念、其在Web和Winform中的应用以及如何利用它们。 1. **多线程概念**: - 在计算机编程中,多线程是指一个程序内同时执行的多个独立流程。这使得应用程序能够并发处理不同的任务,...

    WinForm C#多线程等待窗体

    在C#编程中,Windows Forms(WinForm)应用程序...总的来说,掌握在WinForm中使用多线程和等待窗体是提高用户体验和保证程序稳定性的关键技能。通过正确地管理线程和资源,我们可以构建出更高效、更健壮的应用程序。

    winform实现多线程下载

    本文将深入探讨如何在WinForm应用程序中实现多线程下载。 首先,了解多线程的基本概念至关重要。线程是程序执行的最小单位,一个进程中可以有多个线程同时运行。在下载场景中,多线程可以让我们同时从服务器获取多...

    解决winform编程中使用线程界面卡死的案例

    在多线程环境中,我们可以使用委托将UI更新的方法传递给后台线程,然后在后台线程执行完毕后安全地回调到主线程来更新UI。 以下是一个简单的示例: ```csharp using System; using System.Windows.Forms; using ...

    winform之多线程编程 (源码)

    在WinForm中,`System.Threading`命名空间提供了`Thread`类,可以用来创建新的线程。 2. `BackgroundWorker`组件:这是.NET提供的一种更易于使用的多线程解决方案,它包含在`System.ComponentModel`命名空间中。`...

    C# winform多线程模板示例,winform多线程例子,C#

    本示例提供了关于C# WinForm中多线程应用的模板和实例,帮助开发者理解和应用这一技术。 1. **线程基础知识** - **主线程与工作线程**:WinForm应用的主线程负责更新UI,而工作线程用于执行非UI相关任务。 - **...

    winform_c#_多线程例子

    1. **BackgroundWorker组件**:这是在Winform中最常见的用于实现多线程的工具。BackgroundWorker组件提供了一个异步执行操作的简单模型,可以在后台线程上运行任务,并通过事件回调到主线程更新UI。在本例子中,可能...

Global site tag (gtag.js) - Google Analytics