处理WinForm多线程程序时的陷阱

与所有的 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 可以产生系统真正的系统窗体。

Published At
Categories with Web编程
Tagged with
comments powered by Disqus