在 Visual Basic 中,单元模型(apartment-model)线程用于保证线程的安全性。单元模型线程的每个线程就好象是一个单元,在该线程上创建的所有对象都存活于该单元之中,它不会觉察到其它单元中的对象。
如图 8.3 所示,Visual Basic 为每个单元复制了一份全局数据,从而减少了由于从多个线程访问全局数据而发生的冲突。
图 8.3 每个线程有一份自己的全局数据副本
这意味着不能使用全局数据在不同线程的对象之间进行通讯。
注意 只有客户端将对象的引用分别传给对方,不同单元的对象才能互相通讯。在这种情况下,使用线程间调度来提供同步。线程间调度几乎和进程间调度一样慢。
注意 除了维护全局数据的独立拷贝以外, Sub Main 过程会在每个新的单元(即每个新的线程)中执行。否则,就无法为每个线程初始化全局数据。在“设计线程安全 DLL”和“设计多线程进程外部件” 中有关于这个问题的进一步的讨论。
Visual Basic 创建的所有部件都使用单元模型,无论该部件是单线程的还是多线程的。单线程的部件只有一个单元,该单元包含部件提供的所有对象。
这就意味着用 Visual Basic 创建的单线程的 DLL 可以安全地用于多线程的客户端。不过,性能与安全是一对矛盾,这种安全性损失了性能。除了当前的客户端线程之外,其它所有的客户端线程的调用都要等待被调度,就好象这些调用是进程外调用。请参阅“设计线程安全的 DLL”。
多线程的进程内部件没有自己的线程。如“设计线程安全的 DLL”中所述,定义每个单元的线程都属于客户端。
与此相对的是,多线程的进程外部件可能有一个固定线程数目的线程池,或者为每个外部创建的对象设置一个线程。请参阅“设计多线程的进程外部件”。
除了为每个线程维护一份全局数据副本之外,Visual Basic 还要维护由 App 等全局对象提供的数据的单独副本。因此 App 对象的 ThreadID 属性总是返回负责处理该属性调用的线程的 Win32 线程 ID。
和 Visual Basic 以前的版本不同,用 Visual Basic 98 编写的工程可以享受到单元模型线程化的优点,而不再需要牺牲诸如窗体和控件之类的可视化元素。Forms、UserControls、UserDocuments 以及 ActiveX 的设计器都是线程安全的。在为 Threading Model 选项选择设置时不必把工程标记为“无用户界面执行”。
要设置 ActiveX DLL 、ActiveX Exe 或 ActiveX 控件工程的线程模型,请按照以下步骤执行:
注意 当改变现有工程的线程模式时,如果工程使用了单线程 ActiveX 控件,则会产生一个错误。Visual Basic 禁止在使用单元线程的工程中使用单线程控件,这将在下面的“将现有的工程转换为单元模式线程化的工程”中说明。
注意 如果为 ActiveX Exe 工程指定了“每个对象对应一个线程”(或者是数目多于一个的线程缓冲区),那么只有外部创建的对象才会在新线程中创建。(请参阅“设计多线程进程外部件”。)对于 ActiveX DLL 和 ActiveX 控件工程,“每个对象对应一个线程”和“线程缓冲池”是不可用的,这是因为线程的创建是由客户应用程序控制的。
重点 对于专业版和企业版的用户,关于选择“每个对象对应一个线程”或“线程缓冲池”的效果的详细讨论,请参阅“设计多线程进程外部件”。
如果需要在网络服务器上创建不用操作者干预就能运行的部件,那么应该设置“无用户界面执行”选项。选择“无用户界面执行”不会影响部件的线程模式。
要将 ActiveX DLL 或 EXE 工程标记为无用户界面执行,请按照以下步骤执行:
重点 选择无用户界面执行选项将牺牲所有需要用户交互的窗体,其中包括消息框和系统错误对话框。请参阅“多线程部件的事件日志”。
Visual Basic 中的单元模型线程化具有下述限制。
通过更改“线程模式”选项并重新编译工程,就可以为现有的工程添加单元线程化功能。更改“线程模式”选项的操作可以参见“为工程选择线程模式”中的说明。对于很多工程而言,这就是所需的全部操作。
如果一个现有的 ActiveX DLL、ActiveX EXE、或 ActiveX 控件工程使用了具有单线程成分的控件,那么把“线程模块”的设置改成“单元线程”就会产生一个错误。由于单线程的 ActiveX 控件在多线程客户应用程序中会引起大量严重的问题,因此 Visual Basic 不允许在 ActiveX 部件工程中使用它们。
如果现有的工程使用了单线程的控件,那么请与代理商联系,以了解是否有单元线程化的版本。
通过手工编辑 .vbp 文件,有可能能骗过 Visual Basic,从而在一个单元线程化的工程中可以使用单线程控件。请不要这么做。单线程 ActiveX 控件可能导致的问题包括:
重点 在用 Visual Basic 或其它开发工具建立的所有多线程部件或应用程序中,单线程控件都可能导致这样或那样的问题。
在单元模型中,重入是指按下述顺序发生的事件:
新的请求需要执行的成员函数可能是别的成员函数,也可能是线程重新进入正在执行的成员函数。在后一种情况下,线程将两次进入同一个成员函数。如果第二个成员函数不放弃对处理器的控制权,则它将在第一个成员之前完成处理。如果它改变了第一个成员函数正在使用的模块级数据,结果可能会很糟。
通过对每个单元进行属性和方法调用的序列化,自动化可以防止重入,除非代码自己放弃了对处理器的控制。可导致放弃对处理器的控制的代码有:
如果希望两个成员函数能够安全地同时执行,除非在编写对象的代码时必须进行特殊的处理,否则在代码中不要放弃对处理器的控制权。
详细信息 单元模型线程对进程内部件和进程外部件的影响是不同的,请参阅“设计线程安全的 DLLs” 和“设计多线程的进程外部件”。