访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问控件非常重要。
.NET Framework 有助于在以非线程安全方式访问控件时检测到这一问题。在调试器中运行应用程序时,如果创建某控件的线程之外的其他线程试图调用该控件,则调试器会引发一个 InvalidOperationException,并提示消息:“从不是创建控件 control name 的线程访问它。”
此异常在调试期间和运行时的某些情况下可靠地发生。强烈建议您在显示此错误信息时修复此问题。在调试以 .NET Framework 2.0 版之前的 .NET Framework 编写的应用程序时,可能会出现此异常。
注意
可以通过将 CheckForIllegalCrossThreadCalls 属性的值设置为 false 来禁用此异常。这会使控件以与在 Visual Studio 2003 下相同的方式运行。
下面的代码示例演示如何从辅助线程以线程安全方式和非线程安全方式调用 Windows 窗体控件。它演示一种以非线程安全方式设置 TextBox 控件的 Text 属性的方法,还演示两种以线程安全方式设置 Text 属性的方法。
- using System;
- using System.ComponentModel;
- using System.Threading;
- using System.Windows.Forms;
- namespace CrossThreadDemo
- {
- public class Form1 : Form
- {
-
- delegate void SetTextCallback(string text);
-
- private Thread demoThread = null;
-
- private BackgroundWorker backgroundWorker1;
- private TextBox textBox1;
- private Button setTextUnsafeBtn;
- private Button setTextSafeBtn;
- private Button setTextBackgroundWorkerBtn;
- private System.ComponentModel.IContainer components = null;
- public Form1()
- {
- InitializeComponent();
- }
- protected override void Dispose(bool disposing)
- {
- if (disposing && (components != null))
- {
- components.Dispose();
- }
- base.Dispose(disposing);
- }
-
- 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();
- }
-
- private void ThreadProcSafe()
- {
- this.SetText("This text was set safely.");
- }
-
-
-
-
-
- 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;
- }
- }
-
-
-
-
- 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.";
- }
- #region Windows Form Designer generated code
- private void InitializeComponent()
- {
- this.textBox1 = new System.Windows.Forms.TextBox();
- this.setTextUnsafeBtn = new System.Windows.Forms.Button();
- this.setTextSafeBtn = new System.Windows.Forms.Button();
- this.setTextBackgroundWorkerBtn = new System.Windows.Forms.Button();
- this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
- this.SuspendLayout();
-
-
-
- this.textBox1.Location = new System.Drawing.Point(12, 12);
- this.textBox1.Name = "textBox1";
- this.textBox1.Size = new System.Drawing.Size(240, 20);
- this.textBox1.TabIndex = 0;
-
-
-
- this.setTextUnsafeBtn.Location = new System.Drawing.Point(15, 55);
- this.setTextUnsafeBtn.Name = "setTextUnsafeBtn";
- this.setTextUnsafeBtn.TabIndex = 1;
- this.setTextUnsafeBtn.Text = "Unsafe Call";
- this.setTextUnsafeBtn.Click += new System.EventHandler(this.setTextUnsafeBtn_Click);
-
-
-
- this.setTextSafeBtn.Location = new System.Drawing.Point(96, 55);
- this.setTextSafeBtn.Name = "setTextSafeBtn";
- this.setTextSafeBtn.TabIndex = 2;
- this.setTextSafeBtn.Text = "Safe Call";
- this.setTextSafeBtn.Click += new System.EventHandler(this.setTextSafeBtn_Click);
-
-
-
- this.setTextBackgroundWorkerBtn.Location = new System.Drawing.Point(177, 55);
- this.setTextBackgroundWorkerBtn.Name = "setTextBackgroundWorkerBtn";
- this.setTextBackgroundWorkerBtn.TabIndex = 3;
- this.setTextBackgroundWorkerBtn.Text = "Safe BW Call";
- this.setTextBackgroundWorkerBtn.Click +=
- new System.EventHandler(this.setTextBackgroundWorkerBtn_Click);
-
-
- this.backgroundWorker1.RunWorkerCompleted +=
- new System.ComponentModel.RunWorkerCompletedEventHandler(
- this.backgroundWorker1_RunWorkerCompleted);
-
- this.ClientSize = new System.Drawing.Size(268, 96);
- this.Controls.Add(this.setTextBackgroundWorkerBtn);
- this.Controls.Add(this.setTextSafeBtn);
- this.Controls.Add(this.setTextUnsafeBtn);
- this.Controls.Add(this.textBox1);
- this.Name = "Form1";
- this.Text = "Form1";
- this.ResumeLayout(false);
- this.PerformLayout();
- }
- #endregion
- [STAThread]
- static void Main()
- {
- Application.EnableVisualStyles();
- Application.Run(new Form1());
- }
- }
- }