访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问控件非常重要。
.NET Framework 有助于在以非线程安全方式访问控件时检测到这一问题。在调试器中运行应用程序时,如果创建某控件的线程之外的其他线程试图调用该控件,则调试器会引发一个 InvalidOperationException,并提示消息:“从不是创建控件 control name 的线程访问它。”
此异常在调试期间和运行时的某些情况下可靠地发生。强烈建议您在显示此错误信息时修复此问题。在调试以 .NET Framework 2.0 版之前的 .NET Framework 编写的应用程序时,可能会出现此异常。
注意
可以通过将 CheckForIllegalCrossThreadCalls 属性的值设置为 false 来禁用此异常。这会使控件以与在 Visual Studio 2003 下相同的方式运行。
对于这个问题的处理,如下所示:
例子的界面如下
注意这里用到的这个backgroundworker控件。
下面的代码很直观的解释了解决此问题的方法:
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public delegate void setTextCallBack(string text);
private Thread demoThread = null;
public Form1()
{
InitializeComponent();
}
#region 非线程安全方式
// 此事件句柄创建一个线程以非安全方式调用一个windows窗体控件,此处会报标题中提到的错误
private void setTextUnsafeBtn_Click(object sender, EventArgs e)
{
this.demoThread = new Thread(new ThreadStart(this.ThreadProcUnsafe));
this.demoThread.Start();
}
//此方法在工作者线程执行并且对TextBox控件作非安全调用
private void ThreadProcUnsafe()
{
this.textBox1.Text = "This text was set unsafely.";
}
#endregion
#region 线程安全方式
// 此事件句柄创建一个以线程安全方式调用windows窗体控件的线程
private void setTextSafeBtn_Click(object sender, EventArgs e)
{
this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafe));
this.demoThread.Start();
}
// 此方法在工作者线程执行并且对TextBox控件作线程安全调用
private void ThreadProcSafe()
{
this.SetText("This text was set safely.");
}
// 如果调用线程和创建TextBox控件的线程不同,这个方法创建
// 代理SetTextCallback并且自己通过Invoke方法异步调用它
// 如果相同则直接设置Text属性
private void SetText(string text)
{
if (this.textBox1.InvokeRequired)
{
setTextCallBack d = new setTextCallBack(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text = text;
}
}
#endregion
#region BackgroudWorker方式
// BackgroundWorker是执行异步操作的首选方式
// 此事件句柄通过调用RunWorkerAsync开启窗体的BackgroundWorker
// 当BackgroundWorker引发RunworkerCompleted事件的时候TextBox
// 控件的Text属性被设置
private void setTextBackgroundWorkerBtn_Click(object sender, EventArgs e)
{
this.backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.textBox1.Text = "This text was set safely by BackgroundWorker.";
}
#endregion
}
}
参考了微软资料和网络。
无意中在网上遇到了总结更好的一篇文章,奇文共赏,感谢作者:
来自菩提树下的杨过http://www.cnblogs.com/yjmyzz/archive/2009/11/25/1610253.html