Visual Basic 的单元模型线程

在 Visual Basic 中,单元模型apartment-model)线程用于保证线程的安全性。单元模型线程的每个线程就好象是一个单元,在该线程上创建的所有对象都存活于该单元之中,它不会觉察到其它单元中的对象。

如图 8.3 所示,Visual Basic 为每个单元复制了一份全局数据,从而减少了由于从多个线程访问全局数据而发生的冲突。

图 8.3 每个线程有一份自己的全局数据副本

这意味着不能使用全局数据在不同线程的对象之间进行通讯。

注意 只有客户端将对象的引用分别传给对方,不同单元的对象才能互相通讯。在这种情况下,使用线程间调度来提供同步。线程间调度几乎和进程间调度一样慢。

注意 除了维护全局数据的独立拷贝以外, Sub Main 过程会在每个新的单元(即每个新的线程)中执行。否则,就无法为每个线程初始化全局数据。在“设计线程安全 DLL”和“设计多线程进程外部件” 中有关于这个问题的进一步的讨论。

单线程的部件与单元模型

Visual Basic 创建的所有部件都使用单元模型,无论该部件是单线程的还是多线程的。单线程的部件只有一个单元,该单元包含部件提供的所有对象。

这就意味着用 Visual Basic 创建的单线程的 DLL 可以安全地用于多线程的客户端。不过,性能与安全是一对矛盾,这种安全性损失了性能。除了当前的客户端线程之外,其它所有的客户端线程的调用都要等待被调度,就好象这些调用是进程外调用。请参阅“设计线程安全的 DLL”。

线程的所有权

多线程的进程内部件没有自己的线程。如“设计线程安全的 DLL”中所述,定义每个单元的线程都属于客户端。

与此相对的是,多线程的进程外部件可能有一个固定线程数目的线程池,或者为每个外部创建的对象设置一个线程。请参阅“设计多线程的进程外部件”。

获取 Win32 的线程 ID

除了为每个线程维护一份全局数据副本之外,Visual Basic 还要维护由 App 等全局对象提供的数据的单独副本。因此 App 对象的 ThreadID 属性总是返回负责处理该属性调用的线程的 Win32 线程 ID。

使用线程的位置

和 Visual Basic 以前的版本不同,用 Visual Basic 98 编写的工程可以享受到单元模型线程化的优点,而不再需要牺牲诸如窗体和控件之类的可视化元素。Forms、UserControls、UserDocuments 以及 ActiveX 的设计器都是线程安全的。在为 Threading Model 选项选择设置时不必把工程标记为“无用户界面执行”。

要设置 ActiveX DLL 、ActiveX Exe 或 ActiveX 控件工程的线程模型,请按照以下步骤执行:

  1. 在“工程”菜单上,选择“<project> 属性”来打开“工程属性”对话框。

  2. 在“通用”选项卡上,选择“线程模块”框中需要的选项。

注意 当改变现有工程的线程模式时,如果工程使用了单线程 ActiveX 控件,则会产生一个错误。Visual Basic 禁止在使用单元线程的工程中使用单线程控件,这将在下面的“将现有的工程转换为单元模式线程化的工程”中说明。

注意 如果为 ActiveX Exe 工程指定了“每个对象对应一个线程”(或者是数目多于一个的线程缓冲区),那么只有外部创建的对象才会在新线程中创建。(请参阅“设计多线程进程外部件”。)对于 ActiveX DLL 和 ActiveX 控件工程,“每个对象对应一个线程”和“线程缓冲池”是不可用的,这是因为线程的创建是由客户应用程序控制的。

重点 对于专业版和企业版的用户,关于选择“每个对象对应一个线程”或“线程缓冲池”的效果的详细讨论,请参阅“设计多线程进程外部件”。

设置无用户界面执行

如果需要在网络服务器上创建不用操作者干预就能运行的部件,那么应该设置“无用户界面执行”选项。选择“无用户界面执行”不会影响部件的线程模式。

要将 ActiveX DLL 或 EXE 工程标记为无用户界面执行,请按照以下步骤执行:

  1. 在“工程”菜单中单击“<project> 属性”打开“工程属性”对话框。

  2. 在“通用”选项卡中,选取“无用户界面执行”,然后单击“确定”。

重点 选择无用户界面执行选项将牺牲所有需要用户交互的窗体,其中包括消息框和系统错误对话框。请参阅“多线程部件的事件日志”。

单元模型线程化的限制

Visual Basic 中的单元模型线程化具有下述限制。

将现有的工程转换为单元模式线程化的工程

通过更改“线程模式”选项并重新编译工程,就可以为现有的工程添加单元线程化功能。更改“线程模式”选项的操作可以参见“为工程选择线程模式”中的说明。对于很多工程而言,这就是所需的全部操作。

如果一个现有的 ActiveX DLL、ActiveX EXE、或 ActiveX 控件工程使用了具有单线程成分的控件,那么把“线程模块”的设置改成“单元线程”就会产生一个错误。由于单线程的 ActiveX 控件在多线程客户应用程序中会引起大量严重的问题,因此 Visual Basic 不允许在 ActiveX 部件工程中使用它们。

如果现有的工程使用了单线程的控件,那么请与代理商联系,以了解是否有单元线程化的版本。

强制使用单线程控件

通过手工编辑 .vbp 文件,有可能能骗过 Visual Basic,从而在一个单元线程化的工程中可以使用单线程控件。请不要这么做。单线程 ActiveX 控件可能导致的问题包括:

重点 在用 Visual Basic 或其它开发工具建立的所有多线程部件或应用程序中,单线程控件都可能导致这样或那样的问题。

重入

在单元模型中,重入是指按下述顺序发生的事件:

  1. 由于调用了对象的属性或方法,单元的执行线程进入该对象的代码部分。

  2. 当线程正位于该属性或方法内时,另一个线程也调用了这个对象的属性或方法,且自动化序列化了该请求,就是说,它将该请求放入队列之中,直至拥有该对象的单元的线程结束目前正在执行的成员函数。

  3. 在终止目前的成员函数之前,线程执行了某些代码,放弃对处理器的控制权。

  4. 自动化通知线程开始执行已序列化的请求,因此该线程重入对象的代码。

新的请求需要执行的成员函数可能是别的成员函数,也可能是线程重新进入正在执行的成员函数。在后一种情况下,线程将两次进入同一个成员函数。如果第二个成员函数不放弃对处理器的控制权,则它将在第一个成员之前完成处理。如果它改变了第一个成员函数正在使用的模块级数据,结果可能会很糟。

通过对每个单元进行属性和方法调用的序列化,自动化可以防止重入,除非代码自己放弃了对处理器的控制。可导致放弃对处理器的控制的代码有:

如果希望两个成员函数能够安全地同时执行,除非在编写对象的代码时必须进行特殊的处理,否则在代码中不要放弃对处理器的控制权。

详细信息 单元模型线程对进程内部件和进程外部件的影响是不同的,请参阅“设计线程安全的 DLLs” 和“设计多线程的进程外部件”。