The MTS programming model uses resource dispensers to model plug-in modules that work with the MTS run time. A resource dispenser shares nondurable resources across objects in an MTS application in a way that promotes efficiency and scalability. The MTS SDK details the requirements of writing resource dispensers for MTS applications. As you saw in Chapter 10, the ODBC Driver Manager is an MTS resource dispenser. One other resource dispenser ships with MTS—the Shared Property Manager (SPM). (Real COM programmers call it "the spam.")
The SPM is a resource dispenser that facilitates the sharing of global memory among objects running inside the same MTS application (process). This is great news for Visual Basic programmers. As you'll recall from Chapter 7, Visual Basic doesn't provide an easy, reliable way to share global data in a multithreaded application. When you declare a public variable in a BAS module, Visual Basic stores the data in thread local storage. Consequently, a set of Visual Basic objects can share BAS-module variables only when they're running in the same single-threaded apartment (STA). If two Visual Basic objects from the same project are running in different STAs, they see a separate instance of each BAS-module variable.
With MTS, you should assume that every activity runs in a separate STA. This means that Visual Basic alone doesn't provide a reasonable way to share data across objects running in separate MTS activities. However, at times you might want all the objects in an MTS application to share a set of global variables. You can solve this problem using the SPM.
Before I explain how to use the SPM, you should note that accessing global data from multiple threads introduces concurrency issues. As you saw in Chapter 7, concurrent access to global data usually requires synchronization. Failure to properly synchronize access to global data can lead to data inconsistency and data corruption in a multithreaded environment.
The SPM provides a simple yet effective form of synchronization. Instead of having stand-alone properties, the SPM requires you to define each property within the scope of a property group. A property group provides a namespace for a set of properties and also lets you indicate how to synchronize access to your data. Figure 12-2 shows how things look from a high-level perspective. The shared property group implements an internal lock manager to synchronize access to property objects inside a group.
Figure 12-2. The shared property manager provides synchronized access to global data through shared properties inside a property group. When you create a property group, you should indicate how you want it to manage locking and its lifetime.
The SPM is a name/value dictionary that lets you create and retrieve properties and property groups by name. Let's look at an example. To use the SPM, you must reference the Shared Property Manager Type Library (Mtxspm.dll) in your MTS DLL project. Before you can create and retrieve properties and property groups, you must create an instance of the shared property group manager, like this:
Dim ObjCtx As ObjectContext Set ObjCtx = GetObjectContext() ' Create shared property group manager instance. Dim spgMgr As SharedPropertyGroupManager Dim spgMgr_ProgID As String spgMgr_ProgID = "MTxSpm.SharedPropertyGroupManager.1" Set spgMgr = ObjCtx.CreateInstance(spgMgr_ProgID)
You can then create a shared property group:
Dim MyGroup As SharedPropertyGroup Dim GroupAlreadyExists As Boolean Set MyGroup = spgMgr.CreatePropertyGroup("MyGroup", _ LockMethod, _ Process, _ GroupAlreadyExists)
The CreatePropertyGroup method takes four parameters. The first parameter lets you pass the name of the property group. If a property group of the same name already exists, the SPM binds you to it. If the property group doesn't exist, the SPM creates it and then binds you to it. The last parameter of CreatePropertyGroup is a Boolean output parameter that tells the caller whether the property group existed before the call.
The second and third parameters let you specify an isolation mode and a release mode for your property group. The isolation mode allows you to establish the locking behavior for the group, while the release mode tells the SPM how long you want the group to remain in memory. Note that both these parameters are passed ByRef and that the SPM ignores their input values if the property group already exists. Only the client that creates the group can set these values. If the property group already exists, the SPM returns the current isolation mode and release mode to the caller in these two parameters as output values.
You can pass the isolation mode value LockSetGet or LockMethod. If you pass LockSetGet, the SPM locks individual properties when they're accessed by a client. This ensures that two clients never read and write to the same property at the same time. In other words, it prevents concurrent access to a shared property object's data, which can result in inconsistency and corruption. However, LockGetSet doesn't prevent other clients from concurrently accessing other properties in the group.
You should pass LockMethod when you want each client to acquire a groupwide exclusive lock across a set of operations. There are a few reasons why you might need to do this. You might want to read a property and then update its value without the risk of another client updating its value between your two operations. Or you might have interdependencies among the properties in a group. In this case, each client might require a higher level of isolation when running a series of updates.
When you create a property group using LockMethod, a client will acquire an exclusive lock that prevents other clients from reading or writing to any property in the group. This lock is held for the duration of the client's method call. When another client attempts to access a property inside a locked group, its call blocks until the lock is released. As with transactions, higher levels of isolation in the SPM result in more blocking and lower levels of concurrency.
When you set the release mode for a group, you can pass a value of either Process or Standard. The value Process indicates that you want the property group to remain in memory for the lifetime of the MTS application. The value Standard indicates that you want the property group to manage its lifetime using standard reference counting. When you pass Standard, the property group releases itself from memory as soon as the last client drops its connection.
Of course, the SPM isn't really useful until you start creating and using shared properties. Now that you know how to create a property group, you can create a property using the CreateProperty method or the CreatePropertyByPosition method. Here's an example of creating a property and setting its default value:
Dim MyProperty As SharedProperty Dim PropertyAlreadyExists As Boolean ' Create and/or bind to property. Set MyProperty = MyGroup.CreateProperty("MyProperty", _ PropertyAlreadyExists) ' Give property a default value when created. If (PropertyAlreadyExists = False) Then MyProperty.Value = "My favorite initial value" End If
As in the case of CreatePropertyGroup, CreateProperty simply binds you to a property if it already exists. You can retrieve an existing property by name or by position. If the property doesn't already exist, the SPM creates it and assigns the default value 0 (as a long integer) to its Value property. If you want a different default value, you should explicitly set it when the property is created.
The Value property of a shared property object is stored as a variant. This gives you quite a bit of flexibility. You can store any variant compliant data type inside a shared property such as Integer, Long, Double, or String. You can also store a variant-compliant array. Here is an example of storing an array of strings in a shared property:
Dim data() As String ReDim data(3) data(0) = "Bob" data(1) = "Carol" data(2) = "Ted" data(3) = "Alice" MyProperty.Value = data
After you create the property, it's pretty easy to read and write to it. You might even be tempted to assign a Visual Basic object reference to a shared property. However, this is a very bad idea. Shared COM objects don't fit into the MTS programming model very well. Because of their problems with thread affinity, shared Visual Basic objects don't fit in at all. In MTS, every object should be owned by one and only one client. However, you can store the state that defines a logical object inside the SPM using primitive data types and arrays. If you do this, you can simply rehydrate objects on an as-needed basis as long as each one lives entirely within the scope of a single activity.
You should also keep in mind that read and write operations in the SPM aren't transacted. While the SPM can provide isolation, it doesn't provide any rollback facilities. If you modify a property from inside an MTS transaction, your changes are permanent, even when you call SetAbort. And don't be fooled into thinking that your property group locks are held to the end of your transactions. The locks in the SPM have nothing to do with the current transaction. The SPM releases its locks as soon as the current method finishes, which in most cases is before the end of your MTS transaction.
COM+ will introduce a new service called the In-Memory Database (IMDB), which will let you share data among objects as the SPM does today. However, the IMDB will be exposed as an OLE DB provider. It will also be a resource manager that can enlist your connections inside an MTS transaction. Your read and write operations to the IMDB will thus be enlisted and reversible in addition to being isolated. A call to SetAbort will undo any change you've made.