一个简单的Form, 按钮btnTest是enabled=false。在btnEnable的Click事件中 创建线程,在线程中尝试设置btnTest.Enabled = true; 发生异常:线程间操作无效: 从不是创建控件“btnTest”的线程访问它。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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 InitializeComponent(); 14 } 15 16 private void btnEnable_Click(object sender, EventArgs e) 17 { 18 thread = new Thread(new ParameterizedThreadStart(EnableButton)); 19 thread.Start(null); 20 } 21 22 private void EnableButton(object o) 23 { 24 // following line cause exception, details as below: 25 //Cross-thread operation not valid: Control 'btnTest' accessed from a thread other than the thread it was created on. 26 btnTest.Enabled = true; 27 btnTest.Text = "Enabled"; 28 } 29 } 30 } |
在默认情况下,C#不准许在一个线程中直接访问或操作另一线程中创建的控件,这是因为访问windows窗体控件本质上是不安全的。
线程之间是可以同时运行的,那么如果有两个或多个线程同时操作某一控件的某状态,尝试将一个控件变为自己需要的状态时, 线程的死锁就可能发生。
为了区别是否是创建该控件的线程访问该控件,Windows窗体控件中的每个控件都有一个InvokeRequired属性,这个属性就是用来检查本控件是否被其他线程调用的属性,当被创建该线程外的线程调用的时候InvokeRequired就为true。有了这个属性我们就可以利用它来做判断了。
光判断出是否被其他线程调用是没有用的,所以windows窗体控件中还有一个Invoke方法可以帮我们完成其他线程对控件的调用。结合代理的使用就可以很好的完成我们的目标。
上面问题的解决方法:
方法一:
public Form1()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = false;
}
//
// Summary:
// Gets or sets a value indicating whether to catch calls on the wrong thread
// that access a control’s System.Windows.Forms.Control.Handle property when
// an application is being debugged.
//
// Returns:
// true if calls on the wrong thread are caught; otherwise, false.
[EditorBrowsable(EditorBrowsableState.Advanced)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[SRDescription("ControlCheckForIllegalCrossThreadCalls")]
[Browsable(false)]
public static bool CheckForIllegalCrossThreadCalls { get; set; }
方法二:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
1 using System; 2 using System.ComponentModel; 3 using System.Threading; 4 using System.Windows.Forms; 5 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方法也可以如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
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),并以此来处理多线程中调用消息处理方法的问题:
1 2 3 4 5 6 7 |
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方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
ISynchronizeInvoke synchronizer = form; if(synchronizer.InvokeRequired) { MethodInvoker invoker = new MethodInvoker(form.method); synchronizer.Invoke(invoker,null); } else { form.method(); } 转自:<a href="http://www.cnblogs.com/Peter-Zhang/archive/2011/09/17/2179892.html">http://www.cnblogs.com/Peter-Zhang/archive/2011/09/17/2179892.html</a> |