Visual Basic 还提供了另一种多执行线程机制。可以将类的 Instancing 属性设置为 SingleUse,这样类的每个实例都将运行在一个独立的部件实例内。尽管部件是单线程的,SingleUse 类的每个实例都有自己的执行线程。
SingleUse 对象所需的开销比多线程部件中的多个对象要多得多。不过,确实有一些情况需要使用 SingleUse 对象:
图 8.9 显示的 SingleUse 对象具有独立的执行线程,这种线程是抢先型多任务的。
图 8.9 具有 SingleUse 对象的部件
要以独占方式使用进程外部件,最好途径是:该部件只包含一个 SingleUse 类,但是附带任意数量的从属对象。
如果部件中的多个类模块的 Instancing 属性被设为 SingleUse,要满足客户端对对象的请求就变得有些复杂,而且这时很难确保一个客户端的对象来自部件的同一个实例。
可以将类想象为一个“空洞”,在创建该类的对象时,“空洞”就被填充。图 8.10 显示的部件提供了三个类 Widget、Gear 和 Sprocket。该部件的每个实例都可以为每种类型提供一个对象。
图 8.10 带有三个 SingleUse 类的部件的实例化
带文字的矩形代表仍可以提供某一类对象的部件。打点的边框则表示部件已经提供了一个这种类型的对象,因此不能再提供同样类型的其它对象。
假设客户端 A 现在要创建一个 Sprocket 对象,那么提供该对象的部件实例将不同于为客户端 A 提供 Widget 对象的部件实例。如果使用 SingleUse 的目的在于使客户端独占同一个部件,那么这种方案将无法保证这一点。
如上所述,独占一个进程外部件的更好方法是只提供一个 SingleUse 对象作为可在外部创建的对象,但从属对象可以是任意数量的。
在图 8.10 中可以看出:客户端 B 在部件的两个实例中都有一个对象,这是由于客户端 B 创建对象的顺序造成的。不过,即使客户端 B 首先创建其 Widget 对象,以便当客户端 B 请求 Sprocket 时部件的第二个实例已在运行,仍然无法保证这个 Sprocket 对象由部件的第二个实例提供。
从客户端 C 可以看出,当部件的多个实例都可以提供一个特定的对象时,ActiveX 规范不能保证哪个实例将提供所需的对象。最好认为这是一种随机选择。
如果客户端创建了 SingleUse 类的两个实例,那么将启动可执行部件的两个实例。而在部件内,可能要创建该类的多个实例。图 8.11 说明了内部对象与外部对象在创建上的区别。
图 8.11 SingleUse 对象的内部创建与外部创建
在部件之内,如果通过带 New 操作符的 Set 语句,或者声明 As New 变量的方法创建该部件自己的 SingleUse 类的对象,该对象将不会填充供客户端创建类实例的“空洞”。在图 8.11 中,部件的内部代码用这种方式创建任意多个 Sprocket 对象。
如果使用 CreateObject 函数来创建 SingleUse 类的对象,则等同于由客户端创建该对象。如果图 8.11 中的客户端现在要创建一个 Sprocket 对象,就必须启动该部件的第二个实例。如果图 8.11 中部件的实例要使用 CreateObject 函数来创建另一个 Sprocket 对象,同样会启动该部件的另一个实例。
一旦某个客户端应用程序创建了一个 SingleUse 类的对象,任何客户端都不可能再从部件的这个实例来创建同一个类的对象,即使第一个客户端释放了该对象也不行。这就是说,一旦“空洞”被填上,就永远也不会变空,即使该对象已经被破坏了。
换句话说,如果将一个类模块定为 SingleUse,那么在该部件的一个实例的生命期内在外部只能创建该类的一个实例。不单对客户端应用程序是这样,对使用 CreateObject 函数的部件也是如此。
在开发环境运行部件时,客户端测试程序在调试会话期间对每种 SingleUse 类都只能创建一个对象。在创建了一个类的实例后,任何创建该类的对象的企图都将导致 429 号错误:“OLE 自动化服务器无法创建对象”。
为了进行调试,可以将 SingleUse 改为 MultiUse。不过,如果要测试部件的 SingleUse 行为的话,必须将这个部件编译成可执行的部件。
详细信息 要让部件既能作为一个可执行文件运行,又能在 Visual Basic 开发环境下运行,请参阅“调试、测试和部署部件”中的“如何测试已编译的部件”。