跨线程间控件调用

  • 格式:doc
  • 大小:84.50 KB
  • 文档页数:6

线程间操作无效的解决办法 访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问控件非常重要。

为了区别是否是创建该控件的线程访问该控件,Windows窗体控件中的每个控件都有一个InvokeRequired属性,这个属性就是用来检查本控件是否被其他线程调用的属性,当被创建该线程外的线程调用的时候InvokeRequired就为true。有了这个属性我们就可以利用它来做判断了。

光判断出是否被其他线程调用是没有用的,所以windows窗体控件中还有一个Invoke方法可以帮我们完成其他线程对控件的调用。结合代理的使用就可以很好的完成我们的目标。 .NET Framework 有助于在以非线程安全方式访问控件时检测到这一问题。在调试器中运行应用程序时,如果创建某控件的线程之外的其他线程试图调用该控件,则调试器会引发一个 InvalidOperationException,并提示消息:“从不是创建控件 control name 的线程访问它。”

此异常在调试期间和运行时的某些情况下可靠地发生。强烈建议您在显示此错误信息时修复此问题。在调试以 .NET Framework 2.0 版之前的 .NET Framework 编写的应用程序时,可能会出现此异常。

现有五种方法解决: 法一: public Form1() { InitializeComponent(); CheckForIllegalCrossThreadCalls = false; }

此种方法存在的问题是你必须使用由微软提供的framework类库才不会报错,否则还是会报错,而且有可能还是一些莫名奇妙的错误。

以下的方法将围绕以下案例讲解: 方法二: 1 using System; 2 using System.Threading; 3 using System.Windows.Forms; 4 5 namespace TestingUIThread 6 { 7 public partial class Form1 : Form 8 { 9 Thread thread= null; 10 11 public Form1() 12 { 13 14 InitializeComponent(); 15 } 16 17 private void btnEnable_Click(object sender, EventArgs e) 18 { 19 thread= new Thread(new ParameterizedThreadStart(EnableButton)); 20 thread.Start(null); 21 } 22 23 private void EnableButton(object o) 24 { 25 EnableButton(); 26 } 27 28 private delegate void EnableButtonCallBack(); 29 30 private void EnableButton() 31 { 32 if (this.btnTest.InvokeRequired) 33 { 34 this.Invoke(new EnableButtonCallBack(EnableButton)); 35 } 36 else 37 { 38 btnTest.Enabled= true; 39 btnTest.Text= "Enabled"; 40 } 41 } 42 } 43 }

此方法用了声明一个代理的方法来解决。 方法三: 通过ISynchronizeInvoke和MethodInvoker.

1 using System; 2 using System.ComponentModel; 3 using System.Threading; 4 using System.Windows.Forms; 6 namespace TestingUIThread 7 { 8 public partial class Form1 : Form 9 { 10 Thread thread= null; 11 12 public Form1() 13 { 14 InitializeComponent(); 15 } 16 17 private void btnEnable_Click(object sender, EventArgs e) 18 { 19 thread= new Thread(new ParameterizedThreadStart(MyEnableButton)); 20 thread.Start(null); 21 } 22 23 private void MyEnableButton(object sender) 24 { 25 ISynchronizeInvoke synchronizer= this; 26 27 if (synchronizer.InvokeRequired) 28 { 29 MethodInvoker minvoker= new MethodInvoker(this.EnableButton); 30 synchronizer.Invoke(minvoker,null); 31 } 32 else 33 { 34 this.EnableButton(); 35 } 36 } 37 38 private void EnableButton() 39 { 40 btnTest.Enabled= true; 41 btnTest.Text= "Enabled"; 42 } 43 } 44 }

MyEnableButton方法也可以如下: private void MyEnableButton(object sender) { if (this.InvokeRequired) { MethodInvoker minvoker= new MethodInvoker(this.EnableButton); this.Invoke(minvoker,null); } else { this.EnableButton(); } } 对于方法三,可用于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;} }

Invoke 同步调用 BeginInvoke和EndInvoke 异步调用,用BeginInvoke()来发送调用,用EndInvoke()来实现等待或用于在完成时进行提示以及收集返回结果。 ISynchronizeInvoke提供了一个普通的标准机制用于在其他线程的对象中进行方法调用。 例如,如果一个对象实现了ISynchronizeInvoke,那么在线程T1上的客户端可以在该对象中调用ISynchronizeInvoke的Invoke()方法。Invoke()方法的实现会阻塞(block)该线程的调用,它将调用打包发送(marshal)到 T2,并在T2中执行调用,再将返回值发送会T1,然后返回到T1的客户端。Invoke()方法以一个代理来定位该方法在T2中的调用,并以一个普通的对象数组做为其参数。 调用者可以检查InvokeRequired属性,因为既可以在同一线程中调用ISynchronizeInvoke也可以将它重新定位(redirect)到其他线程中去。如果InvokeRequired的返回值是false的话,则调用者可以直接调用该对象的方法。

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

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