使用多线程提高 Windows 窗体应用程序的性能时,必须注意以线程安全方式调用控件。

访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问控件非常重要。

.NET Framework 有助于在以非线程安全方式访问控件时检测到这一问题。在调试器中运行应用程序时,如果创建某控件的线程之外的其他线程试图调用该控件,则调试器会引发一个 InvalidOperationException,并提示消息:“从不是创建控件 control name 的线程访问它。”

此异常在调试期间和运行时的某些情况下可靠地发生。强烈建议您在显示此错误信息时修复此问题。在调试以 .NET Framework 2.0 版之前的 .NET Framework 编写的应用程序时,可能会出现此异常。

如何:对 Windows 窗体控件进行线程安全调用注意

可以通过将 CheckForIllegalCrossThreadCalls 属性的值设置为 false 来禁用此异常。这会使控件以与在 Visual Studio 2003 下相同的方式运行。

下面的代码示例演示如何从辅助线程以线程安全方式和非线程安全方式调用 Windows 窗体控件。它演示一种以非线程安全方式设置 TextBox 控件的 Text 属性的方法,还演示两种以线程安全方式设置 Text 属性的方法。

如何:对 Windows 窗体控件进行线程安全调用using System;
如何:对 Windows 窗体控件进行线程安全调用
using System.ComponentModel;
如何:对 Windows 窗体控件进行线程安全调用
using System.Threading;
如何:对 Windows 窗体控件进行线程安全调用
using System.Windows.Forms;
如何:对 Windows 窗体控件进行线程安全调用
如何:对 Windows 窗体控件进行线程安全调用
namespace CrossThreadDemo
}


对 Windows 窗体控件的非线程安全调用

对 Windows 窗体控件的非线程安全调用方式是从辅助线程直接调用。调用应用程序时,调试器会引发一个 InvalidOperationException,警告对控件的调用不是线程安全的。

 

如何:对 Windows 窗体控件进行线程安全调用// This event handler creates a thread that calls a 
如何:对 Windows 窗体控件进行线程安全调用
// Windows Forms control in an unsafe way.
如何:对 Windows 窗体控件进行线程安全调用
private void setTextUnsafeBtn_Click(
如何:对 Windows 窗体控件进行线程安全调用    
object sender, 
如何:对 Windows 窗体控件进行线程安全调用    EventArgs e)
}

对 Windows 窗体控件的线程安全调用

对 Windows 窗体控件进行线程安全调用

  1. 查询控件的 InvokeRequired 属性。

  2. 如果 InvokeRequired 返回 true,则使用实际调用控件的委托来调用 Invoke

  3. 如果 InvokeRequired 返回 false,则直接调用控件。

在下面的代码示例中,此逻辑是在一个称为 SetText 的实用工具方法中实现的。名为 SetTextDelegate 的委托类型封装 SetText 方法。TextBox 控件的 InvokeRequired 返回 true 时,SetText 方法创建 SetTextDelegate 的一个实例,并调用窗体的 Invoke 方法。这使得 SetText 方法被创建 TextBox 控件的线程调用,而且在此线程上下文中将直接设置 Text 属性。

如何:对 Windows 窗体控件进行线程安全调用// This event handler creates a thread that calls a 
如何:对 Windows 窗体控件进行线程安全调用
// Windows Forms control in a thread-safe way.
如何:对 Windows 窗体控件进行线程安全调用
private void setTextSafeBtn_Click(
如何:对 Windows 窗体控件进行线程安全调用    
object sender, 
如何:对 Windows 窗体控件进行线程安全调用    EventArgs e)
}

如何:对 Windows 窗体控件进行线程安全调用// This method demonstrates a pattern for making thread-safe
如何:对 Windows 窗体控件进行线程安全调用
// calls on a Windows Forms control. 
如何:对 Windows 窗体控件进行线程安全调用
//
如何:对 Windows 窗体控件进行线程安全调用
// If the calling thread is different from the thread that
如何:对 Windows 窗体控件进行线程安全调用
// created the TextBox control, this method creates a
如何:对 Windows 窗体控件进行线程安全调用
// SetTextCallback and calls itself asynchronously using the
如何:对 Windows 窗体控件进行线程安全调用
// Invoke method.
如何:对 Windows 窗体控件进行线程安全调用
//
如何:对 Windows 窗体控件进行线程安全调用
// If the calling thread is the same as the thread that created
如何:对 Windows 窗体控件进行线程安全调用
// the TextBox control, the Text property is set directly. 
如何:对 Windows 窗体控件进行线程安全调用

如何:对 Windows 窗体控件进行线程安全调用
private void SetText(string text)
}

使用 BackgroundWorker 进行的线程安全调用

在应用程序中实现多线程的首选方式是使用 BackgroundWorker 组件。BackgroundWorker 组件使用事件驱动模型实现多线程。辅助线程运行 DoWork 事件处理程序,创建控件的线程运行 ProgressChangedRunWorkerCompleted 事件处理程序。注意不要从 DoWork 事件处理程序调用您的任何控件。

下面的代码示例不异步执行任何工作,因此没有 DoWork 事件处理程序的实现。TextBox 控件的 Text 属性在 RunWorkerCompleted 事件处理程序中直接设置。

如何:对 Windows 窗体控件进行线程安全调用// This event handler starts the form's 
如何:对 Windows 窗体控件进行线程安全调用
// BackgroundWorker by calling RunWorkerAsync.
如何:对 Windows 窗体控件进行线程安全调用
//
如何:对 Windows 窗体控件进行线程安全调用
// The Text property of the TextBox control is set
如何:对 Windows 窗体控件进行线程安全调用
// when the BackgroundWorker raises the RunWorkerCompleted
如何:对 Windows 窗体控件进行线程安全调用
// event.
如何:对 Windows 窗体控件进行线程安全调用
private void setTextBackgroundWorkerBtn_Click(
如何:对 Windows 窗体控件进行线程安全调用    
object sender, 
如何:对 Windows 窗体控件进行线程安全调用    EventArgs e)
}

Windows 窗体上的 ActiveX 控件

如果在窗体上使用 ActiveX 控件,则在调试器下运行时可能会收到线程间 InvalidOperationException。发生这种情况时,ActiveX 控件不支持多线程处理。有关使用 Windows 窗体的 ActiveX 控件的更多信息,请参见 Windows 窗体和非托管应用程序

如果您使用的是 Visual Studio,则可以通过禁用 Visual Studio 宿主进程来防止此异常发生。

有关更多信息,请参见How to: Disable the Hosting Process如何:禁用宿主进程

可靠编程

如何:对 Windows 窗体控件进行线程安全调用警告

使用任何一种多线程时,代码都容易产生非常严重而复杂的 bug。有关更多信息,请在实现使用多线程的任何解决方案之前参见托管线程处理的最佳做法


相关文章: