WinFomr UI 多线程 (线程间操作无效)(转载)

一个简单的Form, 按钮btnTest是enabled=false。在btnEnable的Click事件中 创建线程,在线程中尝试设置btnTest.Enabled = true; 发生异常:线程间操作无效: 从不是创建控件“btnTest”的线程访问它。

代码如下:

 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 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;
 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方法也可以如下:

        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();
}
作者: Peter
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值