原文http://www.cnblogs.com/SkySoot/archive/2012/03/14/2396552.html

我们先来看一段运行时会抛出 InvalidOperationException 异常的代码段:

 1 public partial class TestThread : Form
 2 {
 3     public TestThread()
 4     {
 5         InitializeComponent();
 6     }
 7  
 8     Thread thread;
 9  
10     void SetText(string str)
11     {
12         this.textBox1.Text = str;
13     }
14  
15     void DoSomething()
16     {
17         // do......
18         SetText("XXX");
19     }
20  
21     private void buttonX1_Click(object sender, EventArgs e)
22     {
23         thread = new Thread(new ThreadStart(DoSomething));
24         thread.IsBackground = true;
25         thread.Start();
26     }
27 }

image

在VS2005或者更高版本中,只要不是在控件的创建线程(一般就是指UI主线程)上访问控件的属性就会抛出这个错误,解决方法就是利用控件提供的Invoke和BeginInvoke把调用封送回UI线程,也就是让控件属性修改在UI线程上执行.

下面给出修改后正确调用的代码:

比较直接的修改方法像这样:

 1 public partial class TestThread : Form
 2 {
 3     delegate void ChangeTextBoxValue(string str); // 新增委托代理
 4  
 5     public TestThread()
 6     {
 7         InitializeComponent();
 8     }
 9  
10     Thread thread;
11  
12     void SetText(string str)
13     {
14         this.textBox1.Text = str;
15     }
16  
17     void DoSomething()
18     {
19         // do......
20         this.BeginInvoke(new ChangeTextBoxValue(SetText),"XXX"); // 也可用 this.Invoke调用
21     }
22  
23     private void buttonX1_Click(object sender, EventArgs e)
24     {
25         thread = new Thread(new ThreadStart(DoSomething));
26         thread.IsBackground = true;
27         thread.Start();
28     }
29 }

不过,要考虑到也许程序中不止子线程会调用 DoSomething()方法,也许主线程或其他模块多处调用这个方法,最漂亮的改法是这样:

 1 public partial class TestThread : Form
 2 {
 3     delegate void ChangeTextBoxValue(string str);
 4  
 5     public TestThread()
 6     {
 7         InitializeComponent();
 8     }
 9  
10     Thread thread;
11  
12     void SetText(string str)
13     {
14         if (this.InvokeRequired) // 获取一个值指示此次调用是否来自非UI线程
15         {
16             this.Invoke(new ChangeTextBoxValue(SetText), str);
17         }
18         else
19         {
20             this.textBox1.Text = str;
21         }            
22     }
23  
24     void DoSomething()
25     {
26         // do......
27         SetText("ABCDEFG");
28     }
29  
30     private void buttonX1_Click(object sender, EventArgs e)
31     {
32         thread = new Thread(new ThreadStart(DoSomething));
33         thread.IsBackground = true;
34         thread.Start();
35     }
36 }

this.Invoke(new ChangeTextBoxValue(SetText), str) // 在拥有控件的基础窗口句柄的线程上,用指定的参数列表执行指定委托

this.BeginInvoke(new ChangeTextBoxValue(SetText), str); // 在创建控件的基础句柄所在线程上,用指定的参数异步执行指定委托。

无论是同步还是异步,单步跟踪调试代码会发现,这些方法还是会回到UI线程执行,其中通过了代理而已.

这两个方法向UI线程的消息队列中放入一个消息,当UI线程处理这个消息时,就会在自己的上下文中执行传入的方法,换句话说凡是使用BeginInvoke和Invoke调用的线程都是在UI主线程中执行的,所以如果这些方法里涉及一些静态变量,不用考虑加锁的问题.