MORE INFORMATION
Global variables in Visual Basic are slightly different in
scope from global variables in a C++ program. Instead of having one copy of a
global variable that all instances of a class share, Visual Basic stores
a unique copy of each global variable per thread in thread local storage (TLS).
If two instances of the same
Apartment class are bound to different
STA threads, each STA (and, therefore, each of the two
instances of that class) has its own copy of the global variable. Changes that one instance (one STA) makes
to the value are not seen by the other instance
(a different STA) because each TLS is unique to an STA.
When you configure
a Visual Basic
Apartment component in COM+, the COM+ activity model may cause a global
variable to become corrupted. COM+ binds up to five activities to one of its
pooled STA threads, depending on the load. Therefore, five different logical
chains of execution, or five callers, can all be bound to one STA
thread.
More than one thread cannot run in the same STA at the same time. When
a COM method makes a call to an object that is bound to an STA, this call is
packaged as a WM_USER message that is posted to the hidden window of that STA.
When an outgoing apartment method call, or a cross-process remote procedure
call (RPC) is made, the COM channel creates a separate thread to make the
actual method call, and then enters the message loop and services the next
message in the queue.
Because the STA guards data against concurrency but not
against reentrancy, a second caller (activity) on that thread uses the CPU and
can gain access to the global TLS data while the first caller is still in progress.
This second caller has access to the same data in TLS that the preempted first
call may have changed. If the data is changed while the second message is
being processed, the data reflects this change immediately. When the first call
returns, it finds that the data is different from what appeared previously and
determines that the data has been corrupted.
For example, configure a
Visual Basic
Apartment component that has a class named
MyVBClass in COM+.
MyVBClass contains code to change the global integer variable g_MyVal. The
first caller, client X, creates an instance of
MyVBClass, and then calls a method to set g_MyVal to
5. In the same method,
MyVBClass performs either a cross-apartment or cross-process RPC call to
another COM+ component. The COM channel creates an additional thread to make
the actual RPC call, and then enters the COM message pump and handles a pending
call from client Y.
Client Y sets g_MyVal to
10, completes its work, and then returns. When the call of client Y is
completed, the outgoing method call of client X uses the processor again. Client X's
call reads g_MyVal and expects the value that it set previously (
5). However, the value
10 is received. To client X, this is a corrupted value.
In
any multithreaded execution environment, Visual Basic globals are unique in
the TLS of each thread. However, you have little control over which STA your
instances are bound to, unless you explicitly configure your component to use
COM+ concurrency. However, COM+ concurrency only guarantees that all instances
in the same logical thread of execution are bound to the same STA. When you
start a different thread of execution, or when you do not use COM+
synchronization, your instances may not be bound to an STA where the correct
global representation is stored. This is another way that you can experience problems when you use globals in Visual
Basic.
Microsoft recommends that you do not use
global variables in Visual Basic components that run in any multithreaded
environment such as IIS and COM+. Instead, you can declare private variables in
the class, and then expose them by using public properties as shown in the
following sample code.
Avoid Global Variable Workaround
' Private variable for storing the value
Private intMyVal As Integer
' Public property to retrieve the value
Public Property Get MyValue() As Integer
MyValue = intMyVal
End Property
' Public property to assign the value
Public Property Set MyValue(myVal As Integer)
intMyVal = myVal
End Property
EmulateMTSBehavior Workaround
Another way to work around this problem is to set the
EmulateMTSBehavior registry key. This setting causes an instance of an object to be created on its own thread. Therefore, you no longer experience the problem of having two instances that share the same TLS data.
Note After you reach the 100 threads with the
EmulateMTSBehavior registry key, the behavior returns to the default setting and you can reuse the threads.
Important Depending on your
architecture, setting this registry key may adversely affect the
performance of your COM+ application. If you decide to set this registry key, thoroughly stress test your application to verify that the performance is
acceptable.
For additional information, click the following article number to view the article in the Microsoft Knowledge Base:
303071
INFO: Registry Key for Tuning COM+ Thread and Activity
For more information
about how to set the
EmulateMTSBehavior registry key, visit the following Microsoft Web site:
Intrinsic Visual Basic Objects
In Visual Basic code, the
App, the
Err, and the
Printer intrinsic Visual Basic objects are stored on a per-thread
basis in TLS. These objects can also experience the problems that concurrent access
to TLS can cause. Use these objects very carefully in a multithreaded distributed
environment such as COM+ or Microsoft Internet Information Services (IIS).
App Object
The App object contains read-only information about the Visual Basic application, and also about methods to log events. By using the
App.StartLogging method, a developer can change the properties of the
App object to control how events are logged using the
App.LogEvent method. When you call
App.StartLogging to change the way that events are logged, you affect the global
App object that is shared for each of your components on that thread. If your application uses the
App.StartLogging method, make sure that you call this to set the correct logMode before each call to
App.LogEvent.
Printer Object
When you set the properties of the global
Printer object, all instances of your Visual Basic component on that thread use the new, updated
data. If your Visual Basic components change the
Printer properties, you must verify that the properties are set correctly immediately before you print. You must verify the
Printer properties because
another instance of your component on the same thread may have changed the
properties between the time that they were set and the time that the document was spooled off to
the printer.
Err Object
Visual Basic uses the
Err object to store error information
when a runtime error is raised in your Visual Basic component. Because the
Err object
is stored in TLS, each of your components that are on the same thread share
the same copy of that
Err object.
Verify that you have completed using the
Err
object before you make any blocking calls that may permit another Visual Basic component
on the same thread to run. Do not make any other out-of-process calls,
do not raise events, and do not call
DoEvents from within your error handling routine until
you have completed using the existing data in the
Err object or you may obtain
incorrect results.
Never pass the
Err object outside your Visual Basic
component. The
Err object is also a private Visual Basic object. Do not pass it
outside the component. When a second object gains access to the
Err object, the
Err object
calls back to the original Visual Basic component on a different STA to use the
data. That data may be incorrect for the current object. If you
must pass error information outside your component, pass specific
data such as the
Err.Number and the
Err.Description. For more information, visit the following Microsoft Web site: