访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问控件非常重要。
.NET Framework 有助于在以非线程安全方式访问控件时检测到这一问题。在调试器中运行应用程序时,如果创建某控件的线程之外的其他线程试图调用该控件,则调试器会引发一个 InvalidOperationException,并提示消息:“从不是创建控件 control name 的线程访问它。”
可以通过将 CheckForIllegalCrossThreadCalls 属性的值设置为 false 来禁用此异常。这会使控件以与在 Visual Studio 2003 下相同的方式运行。但是这样做会使程序出现预料之外的问题,如果程序的各线程之间没有互相争抢控件资源的情况,那么可以考虑采用这个办法。例如:
public Form1()
...{
InitializeComponent();
CheckForIllegalCrossThreadCalls = false;
}
下面分别演示一下线程安全与非线程安全访问的实现
这里演示了两种线程安全的控件调用方法:
一种是利用delegate接口以及Invoke方法(黄色背景)
一种是使用BackgroundWorker方法(绿色背景)
目的都是通过调用创建该控件的线程,来对控件进行操作。
namespace CrossThreadDemo
...{
public class Form1 : Form
...{
// 这个 delegate 用来实现对TextBox控件Text属性的异步操作
delegate void SetTextCallback(string text);
// 创建用来调用控件的线程对象
private Thread demoThread = null;
// 利用backgroundworker进行线程安全调用
private BackgroundWorker backgroundWorker1;
private TextBox textBox1;
private Button setTextUnsafeBtn;
private Button setTextSafeBtn;
private Button setTextBackgroundWorkerBtn;
private System.ComponentModel.IContainer components = null;
// 按钮事件,创建非线程安全调用的线程
private void setTextUnsafeBtn_Click(
object sender,
EventArgs e)
...{
this.demoThread =
new Thread(new ThreadStart(this.ThreadProcUnsafe));
this.demoThread.Start();
}
// 采用非线程安全调用
private void ThreadProcUnsafe()
...{
this.textBox1.Text = "This text was set unsafely.";
}
// 按钮事件,创建线程安全调用的线程
private void setTextSafeBtn_Click(
object sender,
EventArgs e)
...{
this.demoThread =
new Thread(new ThreadStart(this.ThreadProcSafe));
this.demoThread.Start();
}
// 调用worker线程,采用线程安全调用
private void ThreadProcSafe()
...{
this.SetText("This text was set safely.");
}
// 使用线程安全方法对窗体控件进行操作
// 首先查询控件的InvokeRequired属性,以此来判断是不是正在从创建这个控件的线程访问该控件
// 如果不是正在从创建这个控件的线程访问该控件,该方法将创建 SetTextDelegate 的一个实例,
// 并调用窗体的 Invoke 方法,这使得 SetText 方法被创建 TextBox 控件的线程调用,而且在此线程上下文中将直接设置 Text 属性
//
// 如果是从创建该控件的线程访问它,则直接对其进行操作
private void SetText(string text)
...{
// InvokeRequired 比较线程ID以及创建控件的线程ID,不同则返回true
if (this.textBox1.InvokeRequired)
...{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] ...{ text });
}
else
...{
this.textBox1.Text = text;
}
}
// 该事件通过调用RunWorkerAsync方法启动BackgroundWorker
// TextBox的Text属性将在 BackgroundWorker 发生 RunWorkerCompleted 事件之后设置好
private void setTextBackgroundWorkerBtn_Click(
object sender,
EventArgs e)
...{
this.backgroundWorker1.RunWorkerAsync();
}
// 通过调用创建控件的线程来更改控件属性,所以是线程安全的
// BackgroundWorker 是首选的异步控件操作方法
private void backgroundWorker1_RunWorkerCompleted(
object sender,
RunWorkerCompletedEventArgs e)
...{
this.textBox1.Text =
"This text was set safely by BackgroundWorker.";
}
}
}