MSDN: ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.chs/dv_fxmclictl/html/138f38b6-1099-4fd5-910c-390b41cbad35.htm
使用多线程提高 Windows 窗体应用程序的性能时,必须注意以线程安全方式调用控件。
示例
访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问控件非常重要。
.NET Framework 有助于在以非线程安全方式访问控件时检测到这一问题。在调试器中运行应用程序时,如果创建某控件的线程之外的其他线程试图调用该控件,则调试器会引发一个 InvalidOperationException,并显示以下消息:“从不是创建控件控件名称 的线程访问它。”
此异常在调试期间和运行时的某些情况下可靠地发生。强烈建议您在显示此错误信息时修复此问题。在调试以 .NET Framework 2.0 版之前的 .NET Framework 编写的应用程序时,可能会出现此异常。
注意: |
|
可以通过将 CheckForIllegalCrossThreadCalls 属性的值设置为 false 来禁用此异常。这会使控件以与在 Visual Studio 2003 下相同的方式运行。 |
下面的代码示例演示如何从辅助线程以线程安全方式和非线程安全方式调用 Windows 窗体控件。它演示一种以非线程安全方式设置 TextBox 控件的 Text 属性的方法,还演示两种以线程安全方式设置 Text 属性的方法。
| Visual Basic |
复制代码 |
Imports System
Imports System.ComponentModel
Imports System.Threading
Imports System.Windows.Forms
Public Class Form1
Inherits Form
Delegate Sub SetTextCallback([text] As String)
Private demoThread As Thread = Nothing
Private WithEvents backgroundWorker1 As BackgroundWorker
Private textBox1 As TextBox
Private WithEvents setTextUnsafeBtn As Button
Private WithEvents setTextSafeBtn As Button
Private WithEvents setTextBackgroundWorkerBtn As Button
Private components As System.ComponentModel.IContainer = Nothing
Public Sub New()
InitializeComponent()
End Sub
Protected Overrides Sub Dispose(disposing As Boolean)
If disposing AndAlso (components IsNot Nothing) Then
components.Dispose()
End If
MyBase.Dispose(disposing)
End Sub
Private Sub setTextUnsafeBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextUnsafeBtn.Click
Me.demoThread = New Thread( _
New ThreadStart(AddressOf Me.ThreadProcUnsafe))
Me.demoThread.Start()
End Sub
Private Sub ThreadProcUnsafe()
Me.textBox1.Text = "This text was set unsafely."
End Sub
Private Sub setTextSafeBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextSafeBtn.Click
Me.demoThread = New Thread( _
New ThreadStart(AddressOf Me.ThreadProcSafe))
Me.demoThread.Start()
End Sub
Private Sub ThreadProcSafe()
Me.SetText("This text was set safely.")
End Sub
Private Sub SetText(ByVal [text] As String)
If Me.textBox1.InvokeRequired Then
Dim d As New SetTextCallback(AddressOf SetText)
Me.Invoke(d, New Object() {[text]})
Else
Me.textBox1.Text = [text]
End If
End Sub
Private Sub setTextBackgroundWorkerBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextBackgroundWorkerBtn.Click
Me.backgroundWorker1.RunWorkerAsync()
End Sub
Private Sub backgroundWorker1_RunWorkerCompleted( _
ByVal sender As Object, _
ByVal e As RunWorkerCompletedEventArgs) _
Handles backgroundWorker1.RunWorkerCompleted
Me.textBox1.Text = _
"This text was set safely by BackgroundWorker."
End Sub
#Region "Windows Form Designer generated code"
Private Sub InitializeComponent()
Me.textBox1 = New System.Windows.Forms.TextBox()
Me.setTextUnsafeBtn = New System.Windows.Forms.Button()
Me.setTextSafeBtn = New System.Windows.Forms.Button()
Me.setTextBackgroundWorkerBtn = New System.Windows.Forms.Button()
Me.backgroundWorker1 = New System.ComponentModel.BackgroundWorker()
Me.SuspendLayout()
Me.textBox1.Location = New System.Drawing.Point(12, 12)
Me.textBox1.Name = "textBox1"
Me.textBox1.Size = New System.Drawing.Size(240, 20)
Me.textBox1.TabIndex = 0
Me.setTextUnsafeBtn.Location = New System.Drawing.Point(15, 55)
Me.setTextUnsafeBtn.Name = "setTextUnsafeBtn"
Me.setTextUnsafeBtn.TabIndex = 1
Me.setTextUnsafeBtn.Text = "Unsafe Call"
Me.setTextSafeBtn.Location = New System.Drawing.Point(96, 55)
Me.setTextSafeBtn.Name = "setTextSafeBtn"
Me.setTextSafeBtn.TabIndex = 2
Me.setTextSafeBtn.Text = "Safe Call"
Me.setTextBackgroundWorkerBtn.Location = New System.Drawing.Point(177, 55)
Me.setTextBackgroundWorkerBtn.Name = "setTextBackgroundWorkerBtn"
Me.setTextBackgroundWorkerBtn.TabIndex = 3
Me.setTextBackgroundWorkerBtn.Text = "Safe BW Call"
Me.ClientSize = New System.Drawing.Size(268, 96)
Me.Controls.Add(setTextBackgroundWorkerBtn)
Me.Controls.Add(setTextSafeBtn)
Me.Controls.Add(setTextUnsafeBtn)
Me.Controls.Add(textBox1)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
Me.PerformLayout()
End Sub 'InitializeComponent
#End Region
<STAThread()> _
Shared Sub Main()
Application.EnableVisualStyles()
Application.Run(New Form1())
End Sub
End Class
|
| C# |
复制代码 |
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());
}
}
}
|
对 Windows 窗体控件的非线程安全调用
对 Windows 窗体控件的非线程安全调用方式是从辅助线程直接调用。调用应用程序时,调试器会引发一个 InvalidOperationException,警告对控件的调用不是线程安全的。
| Visual Basic |
复制代码 |
Private Sub setTextUnsafeBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextUnsafeBtn.Click
Me.demoThread = New Thread( _
New ThreadStart(AddressOf Me.ThreadProcUnsafe))
Me.demoThread.Start()
End Sub
Private Sub ThreadProcUnsafe()
Me.textBox1.Text = "This text was set unsafely."
End Sub
|
| C# |
复制代码 |
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.";
}
|
对 Windows 窗体控件的线程安全调用
对 Windows 窗体控件进行线程安全调用
-
查询控件的 InvokeRequired 属性。
-
如果 InvokeRequired 返回 true,则使用实际调用控件的委托来调用 Invoke。
-
如果 InvokeRequired 返回 false,则直接调用控件。
在下面的代码示例中,此逻辑是在一个称为 SetText 的实用工具方法中实现的。名为 SetTextDelegate 的委托类型封装 SetText 方法。TextBox 控件的 InvokeRequired 返回 true 时,SetText 方法创建 SetTextDelegate 的一个实例,并调用窗体的 Invoke 方法。这使得 SetText 方法被创建 TextBox 控件的线程调用,而且在此线程上下文中将直接设置 Text 属性。
| Visual Basic |
复制代码 |
Private Sub setTextSafeBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextSafeBtn.Click
Me.demoThread = New Thread( _
New ThreadStart(AddressOf Me.ThreadProcSafe))
Me.demoThread.Start()
End Sub
Private Sub ThreadProcSafe()
Me.SetText("This text was set safely.")
End Sub
|
| C# |
复制代码 |
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.");
}
|
| Visual Basic |
复制代码 |
Private Sub SetText(ByVal [text] As String)
If Me.textBox1.InvokeRequired Then
Dim d As New SetTextCallback(AddressOf SetText)
Me.Invoke(d, New Object() {[text]})
Else
Me.textBox1.Text = [text]
End If
End Sub
|
| C# |
复制代码 |
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;
}
}
|
使用 BackgroundWorker 进行的线程安全调用
在应用程序中实现多线程的首选方式是使用 BackgroundWorker 组件。BackgroundWorker 组件使用事件驱动模型实现多线程。辅助线程运行 DoWork 事件处理程序,创建控件的线程运行 ProgressChanged 和 RunWorkerCompleted 事件处理程序。注意不要从 DoWork 事件处理程序调用您的任何控件。
下面的代码示例不异步执行任何工作,因此没有 DoWork 事件处理程序的实现。TextBox 控件的 Text 属性在 RunWorkerCompleted 事件处理程序中直接设置。
| Visual Basic |
复制代码 |
Private Sub setTextBackgroundWorkerBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextBackgroundWorkerBtn.Click
Me.backgroundWorker1.RunWorkerAsync()
End Sub
Private Sub backgroundWorker1_RunWorkerCompleted( _
ByVal sender As Object, _
ByVal e As RunWorkerCompletedEventArgs) _
Handles backgroundWorker1.RunWorkerCompleted
Me.textBox1.Text = _
"This text was set safely by BackgroundWorker."
End Sub
|
| C# |
复制代码 |
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.";
}
|
Windows 窗体上的 ActiveX 控件
如果在窗体上使用 ActiveX 控件,则在调试器下运行时可能会收到线程间 InvalidOperationException。发生这种情况时,ActiveX 控件不支持多线程处理。有关使用 Windows 窗体的 ActiveX 控件的更多信息,请参见 Windows 窗体和非托管应用程序。
如果您使用的是 Visual Studio,则可以通过禁用 Visual Studio 宿主进程来防止此异常发生。
有关更多信息,请参见如何:禁用宿主进程.
可靠编程
警告: |
|
使用任何一种多线程时,代码都容易产生非常严重而复杂的 bug。有关更多信息,请在实现使用多线程的任何解决方案之前参见 托管线程处理的最佳做法。 |
请参见
概念
Windows 窗体和非托管应用程序
参考
BackgroundWorker