February 25, 2002

VB Threading

In apartment threading each thread belongs to its own apartment, and each apartment gets its own copy of global data and global objects in VB's implementation of apartment-model threading. Objects on the same thread can share variables declared as Public in a standard module. The danger is that you have little control over which objects will share a thread. As a general rule, you should avoid creating your own global variables in your multithreaded applications unless you know how VB will interpret them. Global variables can be useful, however, if you think of them in terms of apartments. For example, consider the App object, which VB creates in each apartment automatically. You can use the App.ThreadID property to obtain the Win32 thread ID that uniquely identifies each thread. The thread manager described in this article uses the thread ID to keep track of the objects it manages.

Note that the Instancing property of an object in an ActiveX component affects where VB instantiates the object. If the object's Instancing property is set to SingleUse (or GlobalSingleUse), VB creates a new process (with a new primary thread) each time you use New or CreateObject in the client to instantiate the object. VB also creates that object in a new process if an object in the server creates another object using CreateObject, and that object has an Instancing property of SingleUse. This option isn't available for ActiveX DLLs, however. This makes sense because an in-process component is created "in the same process" as its client. You can implement a form of "multithreading" without diving into threads too deeply by setting an object's Instancing property to SingleUse. This approach forces VB to create each object in its own process, which requires extra resources.

The Project Properties dialog determines which thread VB will create an object on when you set an object class's Instancing property to MultiUse or GlobalMultiUse. Visual Basic provides three options for assigning objects to threads for out-of-process components: thread per object, single thread of execution, and thread pool.
Single-threaded execution is the default when creating an ActiveX EXE component, and you use this for creating most of your components. Multithreaded components require that you choose one of the other options, however. You must specify the number of threads in the pool when you compile a component using the thread-pool option. This number often equals the number of processors on a multiprocessor system, but you might want to specify more than one thread on a single-processor system when you know that much of a thread's time will be in a blocked mode, waiting for another server.

VB creates an object on the next thread in a round-robin fashion when a client creates an object with New or CreateObject. If you use the New operator to create an object in a server, then the object is a dependent object and is created on the same thread. An object created within the server using CreateObject is treated as if the client created it, and VB creates it on the next thread. Note that each thread gets its own copy of global data and objects. You have no way of predicting which objects, aside from dependent objects, will share a thread and the global information. Life is simpler when you compile using the thread-per-object option. This option allows you to create each object on its own thread, rather than the next thread in the pool. Each object also has its own App object, its own copy of global data, and its own copy of other global objects. The downside of this approach is that you have no control over how many objects (threads) are created.