与所有的 UI 开发平台一样, .NET 下线程开发图形界面同样要遵循一个基本原则:就是对 UI 对象的操作一定要在产生该 UI 对象的线程里进行(该线程称作 UI 线程),因为大部分 UI 对象都不是线程安全的。
在 .NET 中,把调用调用放在 UI 线程里执行是通过 Form 类及其子类的 Invoke ()方法实现的(具体的过程请参考其他资料),可以这样做是因为 Form 对象保存了创建它的线程的信息,而且 Form 类有一个 bool 类型的属性 InvokeRequired ,可以通过它查看当前线程是否为创建该 Form 对象的线程( UI 线程)——如果为 true ,则表示当前线程不是 UI 线程,反之则是。下面提供一个例子:
using System.Threading;
using System.Windows.Forms;
namespace csharpTest
{
public class TestForm : Form
{
private Form form1;
private Form form2;
public static void Main ()
{
TestForm tf = new TestForm();
tf.Show();
tf.UIThread();
Application.Run();
}
public void UIThread()
{
form1 = new Form();
form2 = new Form();
form2.Show(); // 这里是关键
form1.Show();
Thread thread = new Thread( new ThreadStart(WorkerThread));
thread.Start();
}
public void WorkerThread()
{
if (form2.InvokeRequired)
form2.Invoke( new MethodInvoker(WorkerThread));
else
{
form1.Text = "This is from WorkerThread.";
}
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
base .OnClosing (e);
Application.Exit();
}
}
}
TestForm 里有两个需要注意的方法, UIThread ——用来模拟 UI 线程, WorkerThread ——用来模拟用户线程, UIThread 中实例化了成员 form1 与 form2 , ** 并调用了它们的 Show 方法 ** ,在 WorkerThread 中改变 form1 的 Text 属性。请注意 WorkerThread 里有个技巧, if (form2.InvokeRequired) 即如果当前线程不是创建该 form2 的线程,则将方法通通过过 Invoke 方法放到 UI 线程里去执行。但就是这里 ** 问题出现了 ** 。 form1 和 form2 都是在 UIThread 里建立的,所以它们保存的线程的信息应该是一样的。所以 form1.InvokeRquired 和 form2.InvokeRquired 的值在任何线程里都是一样的,即在 WorkerThread 中 InvokeRquire 的值都应该是 true (因为在不同的线程里)。但是如果注释掉 form2.Show() 的话 form2.InvokeRquired 在 WorkerThread 中的值却是 false (在 vs.net 中调试看到),怎么会这样呢?而且如果不经过判断直接在 WorkerThread 里调用 form2 对象的 Invoke 的话…………居然会 ** 抛出异常 ** ——“ ** 在创建窗口句柄之前,不能在控件上调用 Invoke 或 InvokeAsync ** ”
分析一下该异常的信息,在 win32 里每一个窗体都有一个窗体句柄,是该窗体在建立时系统分配的,但我们确实在 UI 线程里建立了 form2 对象的。这里有个误区 .Net 里的 Form 对象并不是和 win32 的窗体对象完全对应的。本人窃以为, ** 产生一个 Form 类的实例时,只是产生了一个内存中的普通的对象 ** ,并不产生系统窗体(好像叫做 User 对象吧),只有它第一次呈现在屏幕上(或称作创建)时,才产生系统里表示窗体的 User 对象且分配句柄,对应的 WIN32 API 的 CreateWindow() 方法大概也在这个时候执行(先声明:本人对 WIN32 AP 并不熟悉,所以这里如果有什么不妥的话请大家指正)
只有 .NET 里的 form 对象调用某种方法使系统产生真正的窗体时, form 才会有创建它的线程的信息,且 InvokeRquired 才有效,即才能调用 form 的 Invoke 方法。不过我还没弄清楚哪几个方法可以做到。据我所知 Show, CreateGraphics 可以产生系统真正的系统窗体。