原文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 }
在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主线程中执行的,所以如果这些方法里涉及一些静态变量,不用考虑加锁的问题.