Visual Basic Concepts
Designing Multithreaded Out-of-Process Components
""You can create a multithreaded out-of-process component that utilizes apartment-model threading. Objects the component provides can run on different threads of execution.
Visual Basic provides three models for assigning objects to threads in out-of-process components. You can select one of these models using the Unattended Execution box, on the General tab of the Project Properties dialog box.
Thread assignment model | Unattended Execution settings |
One thread of execution | Select the Thread Pool option with one thread. |
Thread pool with round-robin thread assignment | Select the Thread Pool option and specify the number of threads to allow. |
Every externally created object is on its own thread | Select the Thread per Object option. |
Note The model you select cannot be changed at run time.
Like other private objects, forms reside on the thread where they were created. Private objects cannot be created with the CreateObject function, so they cannot be used to start new threads.
Important In a multithreaded executable, Sub Main executes independently for each new thread. Visual Basic provides no way to determine which thread was created first.
One Thread of Execution
This is the default setting, when you select Unattended Execution. This option allows you to compile components authored with earlier versions of Visual Basic, without having to modify them to take threading into account.
By recompiling a component with the Unattended Execution option, you remove any possibility that it will show message boxes that require operator intervention. Instead, such messages can be logged to the Windows NT event log (or to a file of your choice), as described in "Event Logging for Multithreaded Components."
Round-Robin Thread Pool
As clients request objects, Visual Basic creates each object on the next thread in the pool, starting over with the first thread when it reaches the end of the pool.
For example, Figure 8.6 shows a component with three externally creatable classes (that is, classes whose Instancing property is set to any value except Private or PublicNotCreatable). The classes are named A, B, and C. To keep track of which object was requested by which client, the circle that represents each object contains the class name and the number of the client that created the instance.
Figure 8.6 Round-robin thread pool with five threads and four clients
Four clients have created instances of these classes. Client 1 requested the first object, of class A, which was created on thread T1. Client 2 then requested an object of class A, which was created on thread T2, and so on. Some clients have created multiple instances of the same class — for example client 1, which is using a total of four instances of class A.
Figure 8.6 illustrates an important point about the round-robin threading model: You cannot predict which objects will be on the same thread — and therefore sharing global data. Objects used by different clients may share global data, while objects used by the same client may not.
For example, the objects on thread T1 are used by clients 1, 2, and 3, yet they share the thread’s instance of global data. Objects used by client 1 can be found on threads T1, T3, T4, and T5. Of those objects, only the two on thread T5 share global data.
In addition, calls to the properties and methods of objects on the same thread will be serialized, and can therefore block each other — for example, if client 1 calls a method of its A object on thread T1 just before client 2 calls a method of its B object on thread T1, client 2 will have to wait until the call made by client 1 finishes.
The most serious aspect of this serialization of calls and sharing of global data is that the designer of a client application cannot predict when objects will share global data, or when they will block each other. The behavior of the round-robin thread pool algorithm is therefore referred to as nondeterministic.
Load Balancing
Another important point about round-robin threading is illustrated by Figure 8.7.
Figure 8.7 Round-robin threading lacks load balancing
Clients 2 and 3 have released their objects and closed. Thread T1 has only one object on it, and thread T2 has none — yet the next object will still be created on thread T3. In fact, objects will be created on threads T3, T4, and T5 before an object is created on either of the under-utilized threads!
One Important Advantage
The advantage that the round-robin threading model offers in exchange for these drawbacks is that it puts a limit on the total number of threads. This is a significant advantage, because multiprocessing works best if the total number of active threads roughly matches the number of processors.
For More Information Externally creatable objects are discussed in "Externally Creatable Objects," in "General Principles of Component Design."
Thread Per Object
As clients request objects, each object is created on a new thread. When the last client releases its last reference to objects on that thread, the thread is terminated.
Note that a client may have references to several objects on a thread. Suppose the client creates a Widget object, which in turn creates two dependent objects, a Sprocket and a Gear. If the client gets reference to the Sprocket object and then releases the Widget, the thread must continue to exist until the Sprocket is released.
Retaining unused threads in this fashion will eventually degrade system performance, so avoiding dangling object references is critical when you’re using a multithreaded component.
No Control Over Number of Threads
The big drawback to the thread-per-object model is that you have no control over the number of objects (and hence threads) that clients create. Creating too many threads will bog down the operating system in thread-maintenance overhead.
Too many active threads — that is, threads that are actively executing code — will bog down the operating system even more quickly. Generally speaking, you want about the same number of active threads as you have processors, to guarantee that there’s never an idle processor. When you have just one processor, that’s not very many active threads.
Internal and External Object Creation
If a client requests an instance of any externally creatable class, Visual Basic will create the object either on the next thread in the pool, or on a new thread. However, if that object turns around and requests an instance of an externally creatable class, what happens will depend on how the second instance is created:
If the new object is created using the New operator, or with a variable declared As New, then the sibling object will not be on another thread. It will share the thread (and global data) of the object that created it, just like any dependent object.
If the new object is created using the CreateObject function, it will be as if a client had requested the object. Visual Basic will create the object either on the next thread in the pool or on a new thread, depending on the thread assignment model.
Note If an object uses CreateObject to create an object on another thread, any calls it makes to the properties and methods of that object will be subject to cross-thread marshaling.
For More Information Dependent objects are discussed in "Dependent Objects," in "General Principles of Component Design." In coding the properties and methods of your objects, it’s very important to observe reentrancy rules, as discussed in "Apartment-Model Threading in Visual Basic."