Visual Basic Concepts
In Visual Basic, apartment-model threading is used to provide thread safety. In apartment-model threading, each thread is like an apartment — all objects created on the thread live in this apartment, and are unaware of objects in other apartments.
Visual Basic’s implementation of apartment-model threading eliminates conflicts in accessing global data from multiple threads by giving each apartment its own copy of global data, as shown in Figure 8.3.
Figure 8.3 Each thread has its own copy of global data
This means that you cannot use global data to communicate between objects on different threads.
Note Objects in different apartments can only communicate with each other if a client passes them references to each other. In this case, cross-thread marshaling is used to provide synchronization. Cross-thread marshaling is almost as slow as cross-process marshaling.
Note In addition to maintaining a separate copy of global data, a Sub Main procedure is executed for each new apartment (that is, for each new thread). Otherwise, there would be no way to initialize global data for the thread. This is discussed further in "Designing Thread-Safe DLLs" and "Designing Multithreaded Out-of-Process Components."
Single-Threaded Components and the Apartment Model
All components created with Visual Basic use the apartment model, whether they’re single-threaded or multithreaded. A single-threaded component has only one apartment, which contains all the objects the component provides.
This means that a single-threaded DLL created with Visual Basic is safe to use with a multithreaded client. However, there’s a performance trade-off for this safety. Calls from all client threads except one are marshaled, just as if they were out-of-process calls. This is discussed in "Designing Thread-Safe DLLs."
Ownership of Threads
A multithreaded in-process component has no threads of its own. The threads that define each apartment belong to the client, as explained in "Designing Thread-Safe DLLs."
By contrast, a multithreaded out-of-process component may have a thread pool with a fixed number of threads, or a separate thread for each externally created object. This is discussed in "Designing Multithreaded Out-of-Process Components."
Obtaining the Win32 Thread ID
In addition to maintaining a separate copy of your global data for each thread, Visual Basic maintains separate copies of the data supplied by global objects such as the App object. Thus the ThreadID property of the App object will always return the Win32 thread ID of the thread on which the property call was handled.
Where You Can Use Threading
Unlike earlier versions of Visual Basic, projects authored with Visual Basic 6 can take advantage of apartment-model threading without having to suppress visual elements such as forms and controls. Forms, UserControls, UserDocuments, and ActiveX designers are all thread-safe. You can select a setting for the Threading Model option without marking your project for Unattended Execution.
To set the threading model for an ActiveX DLL, ActiveX Exe, or ActiveX Control project
- On the Project menu, select <project> Properties to open the Project Properties dialog box.
- On the General tab, select the desired options in the Threading Model box.
- For ActiveX DLL and ActiveX Control projects, you can select either Apartment Threaded or Single Threaded.
- For ActiveX Exe projects, you can either specify that each new object is created on a new thread (Thread per Object), or limit your component to a fixed pool of threads. A thread pool size of one makes the project single-threaded; a larger thread pool makes the project apartment-threaded.
- For ActiveX DLL and ActiveX Control projects, you can select either Apartment Threaded or Single Threaded.
Note When you change the threading model for an existing project, an error will occur if the project uses single-threaded ActiveX controls. Visual Basic prevents the use of single-threaded controls in apartment-threaded projects, as described in "Converting Existing Projects to Apartment-Model Threading" below.
Note When you specify Thread per Object (or a thread pool greater than one) for an ActiveX Exe project, only externally created objects are created on new threads. (See "Designing Multithreaded Out-of-Process Components.") Thread per Object and Thread Pool are not available for ActiveX DLL and ActiveX Control projects, because thread creation is controlled by the client application.
Important For Professional and Enterprise Edition users, the consequences of selecting Thread per Object or Thread Pool are discussed in detail in "Designing Multithreaded Out-of-Process Components."
Setting Unattended Execution
Unattended Execution allows you to create components that can run without operator intervention on network servers. Selecting Unattended Execution doesn't affect the threading model of your component.
To mark your ActiveX DLL or EXE project for unattended execution
- On the Project menu, click <project> Properties to open the Project Properties dialog box.
- On the General tab, check Unattended Execution, then click OK.
Important Selecting the Unattended Execution option suppresses all forms of user interaction — including message boxes and system error dialogs. This is discussed in "Event Logging for Multithreaded Components."
Limitations of Apartment-Model Threading
The following limitations apply to apartment-model threading in Visual Basic.
- The development environment doesn't support multithreaded debugging. If you have Microsoft Visual Studio, you can debug an apartment-threaded component with the Visual Studio debugger after compiling it to native code with debug information. You will also need a multithreaded client application to test and debug in-process components (.dll and .ocx files).
- MDI parent and child forms must share data in ways that are difficult to make thread-safe. MDI forms, therefore, are not allowed in apartment-threaded projects. On the Project menu, Add MDIForm is unavailable in apartment-threaded ActiveX DLL projects, and in ActiveX Exe projects with Thread per Object selected (or with a thread pool larger than one). If you change the threading model to Apartment Threaded in a project that contains MDI forms, an error occurs.
- In addition to performing poorly, single-threaded ActiveX controls can cause numerous problems in multithreaded clients. Visual Basic therefore prevents the use of single-threaded controls in projects where Threading Model has been set to Apartment Threaded. For details, see "Converting Existing Projects to Apartment-Model Threading" below.
- Friend properties and methods can only be called by objects on the same thread. Because they are not part of an object's public interface, they cannot be marshaled between threads.
- ActiveX Documents in ActiveX Exe projects will not be apartment-model thread-safe unless you select Thread per Object or Thread Pool with a pool size greater than one.
- When a thread shows a form with vbModal, the form will be modal only to code and forms on that thread. Code running in other threads will not be blocked, and forms shown by other threads will remain active.
- Drag and drop between forms and controls will work only if the drag source and the drag target are on the same thread. (OLE drag and drop, however, will work universally.)
- DDE between forms will only work if the forms are on the same thread.
Converting Existing Projects to Apartment-Model Threading
You can add apartment threading to your existing projects by changing the Threading Model option, as described in Selecting a Threading Model for Your Project, and recompiling the project. For many projects, this is all you need to do.
If an existing ActiveX DLL, ActiveX EXE, or ActiveX Control project uses single-threaded constituent controls, attempting to change Threading Model to Apartment Threaded will cause an error. Because of the number and severity of problems that single-threaded ActiveX controls cause for multithreaded clients, Visual Basic does not permit them to be used in ActiveX component projects.
If you have an existing project that employs a single-threaded control, contact the vendor to see whether an apartment-threaded version is available.
Forcing the Use of Single-Threaded Controls
It is possible to trick Visual Basic into using a single-threaded control in an apartment-threaded project, by manually editing the .vbp file. Do not do this. The problems that single-threaded ActiveX controls can cause include:
- If a user tabs to a single-threaded control on a form that's running on a different thread, she will not be able to tab off the control. This occurs because the single-threaded control's thread has no context for the focus on the form's thread. The only way for the user to change the focus in this situation is to use the mouse. Using a single-threaded control as a constituent of an apartment-threaded control causes similar problems.
- In a multithreaded application, activating a form by clicking on a single-threaded control will fail if the form is on a different thread. This also occurs when clicking on a single-threaded constituent of an apartment-threaded control.
- Using a single-threaded OCX in a multithreaded application causes performance problems, because all of the controls the OCX provides must run on the same thread. This means that for controls on forms running in different threads, all communication requires expensive cross-thread calls. An apartment-threaded ActiveX control that uses single-threaded constituent controls will cause similar performance problems.
- In a multithreaded application, the tab key and access keys (for example, Alt+A) will not work for single-threaded controls that are not on the application's main thread. An apartment-threaded ActiveX control that uses single-threaded constituent controls will experience similar problems.
- If your apartment-threaded control sets the Picture property of a single-threaded constituent control (or any other property that takes a Picture object), errors will occur in multithreading clients. This is because the Picture object cannot be marshaled between threads.
Important Single-threaded controls can cause these and other problems in any multithreaded component or application you build, using Visual Basic or any other development tool.
In the apartment model, reentrancy refers to the following sequence of events:
- An apartment’s thread of execution enters an object’s code, because a property or method has been invoked.
- While the thread is in the property or method, another thread invokes a property or method of the object, and Automation serializes this request — that is, it queues the request until the thread that owns the object’s apartment finishes the member it’s currently executing.
- Before the thread reaches the end of the member, it executes code that yields control of the processor.
- Automation tells the thread to begin executing the serialized request, so that the thread reenters the object’s code.
The new request may be for the member the thread was already executing — in which case the thread enters the member a second time — or it may be for another member. If the second member doesn’t yield, it will finish processing before the first member. If it changes module-level data the first member was using, the result may be unfortunate.
By serializing property and method calls for each apartment, Automation protects you from reentrancy — unless your code yields control of the processor. Ways in which your code can yield control of the processor include:
- Calling DoEvents.
- Invoking the properties or methods of an object on another thread, or in another process.
- Raising an event that’s handled by an object on another thread, or in another process.
- Invoking a cross-thread or cross-process method from within a method.
- Showing a form.
Unless you’ve carefully written all of an object’s code so that it doesn’t matter whether two members are executing at the same time, you should not include code that yields control of the processor.
For More Information Apartment-model threading affects in-process and out-of-process components differently, as described in "Designing Thread-Safe DLLs" and "Designing Multithreaded Out-of-Process Components."