多线程操作之窗体控件
ⅠWindows 窗体控件的线程安全性和InvokeRequired属性
Windows 窗体中的控件被绑定到特定的线程,不具备线程安全性。这就是说当我们企图从一个线程中操作在另一个线程中创建的控件时,可能会产生意想不到的错误。为此多线程环境下操作窗体控件时必须注意要使用那些线程安全的方法、成员或事件。
多数控件从Windows基类(比如Control类)那里继承并公开了InvokeRequired 属性。当创建控件句柄的线程和调用线程不同时,该属性指示调用方在调用控件的方法时是否必须调用 Invoke 方法。除InvokeRequired 属性以外,控件上还有以下四个线程安全的方法可供调用:Invoke、BeginInvoke、EndInvoke 和 CreateGraphics。当从另一个线程对除此之外的所有其他成员的进行调用时,应使用这些 Invoke 方法中的一个。
虽然InvokeRequired 属性是线程安全的,但是在使用它的时候仍然需要小心。InvokeRequired 属性为False时,有可能它意味着我们可以不使用Invoke(调用发生在同一线程上),但那只是可能。我们需要知道,对于句柄尚未建立的控件, InvokeRequired 会沿控件的父级链搜索,直到它找到有窗口句柄的控件或窗体为止。如果找不到合适的句柄,InvokeRequired 方法将返回 false。 这时候调用控件上属性、方法或事件就很可能导致在后台线程上创建控件的句柄,从而隔离不带消息泵的线程上的控件并使应用程序不稳定。
通常来说,这种问题仅会发生在下面所说的情况中。那就是我们在应用程序主窗体的构造函数中创建了后台线程(如同在 Application.Run(new MainForm()) 中),并试图在窗体已经显示或取消 Application.Run 之前启动该线程。 因为我们容易忽略一个事实,那就是在启动窗体的Load 事件之前,除非存在强制操作,否则窗体和窗体控件的句柄是不会被创建的。
为了避免上述问题,当 InvokeRequired 在后台线程上返回 false 时,我们往往需要检查 IsHandleCreated 的值来确定控件的句柄是否被创建。如果尚未创建,那么我们必须等待窗体句柄建立(可以通过调用 Handle 属性强制创建句柄、或者等待窗体的 Load 事件)后去才启动后台进程。 更优的选择是使用 SynchronizationContext 返回的 SynchronizationContext,而不是使用控件进行线程间封送处理。
Ⅱ示例:MultiRow多线程加载数据
首先,我们为多线程操作MultiRow提供一个逻辑控制和线程管理的类,本例尽量简单以突出显示我们是怎样使用InvokeRequired 属性和Invoke方法的。程序中我们使用了MultiRow父窗体的Invoke方法而不是MultiRow自己的Invoke方法。
/// 简单的数据加载控制
/// </summary>
public class DataLoadManager
... {
/**//// <summary>
/// 定义结构体,用于保存数据开始和结束行
/// </summary>
struct dataIndexForThread