MTS provides a new concurrency abstraction called an activity. An activity is a logical thread of execution created on behalf of a single base client, as shown in Figure 9-6. Each user owns one or more objects in a single activity, which is isolated from the activities and objects of every other user. To support this concept, the MTS run time creates a new activity whenever a base client creates a new MTS object. Activities keep the programming model fairly simple for MTS programmers. However, behind the scenes the MTS run time maintains a fairly complex thread pooling scheme to accommodate application scalability and increased throughput.
In MTS 2.0, a server application maintains a thread pool that can hold up to 100 single-threaded apartments (STAs). The MTS run time creates a new STA for each new activity until the pool reaches this limit. When the number of activities exceeds the number of STAs, the MTS run time begins to assign multiple activities to each STA. When two activities share the same STA, the MTS run time manages multiplexing STAs (physical threads) in and out of activities (logical threads).
When two activities share the same STA, the code executing on behalf of one user can potentially block the call of another user. However, this should not concern you as an MTS programmer. You should think in terms of logical threads and let the MTS run time manage the physical threads as it sees fit. The designers of MTS have tried to make the threading model as simple as possible. The abstraction of the STA relieves you from worrying about synchronization. The abstraction of the activity builds upon the STA by also relieving you from worrying about the complexities of thread pooling. You need only follow a handful of rules to make the most of the MTS threading model.
Figure 9-6. An activity is a set of MTS objects that belong to a single user. Each activity models a logical thread of execution. The MTS run time manages a physical pool of STAs that are multiplexed in and out of activities on an as-needed basis.
Remember that each activity should represent a set of objects that belong to a single user. You can draw two conclusions from this. First, no two users should connect to objects inside the same activity. Second, you should avoid creating objects in different activities when they belong to the same user.
It's not hard to ensure that the objects in any given activity are accessed by a single user only. As you know, the MTS run time creates a new activity when a base client creates an object. This means that a client application must pass a reference to another client to connect two users to objects in the same activity. You should therefore avoid designs that pass MTS object references between users. Also note that the MTS threading model discourages the use of singletons or any other application designs in which multiple users connect to the same physical object.
The most important thing you need to learn about threading in MTS is how to propagate a new MTS object into an existing activity. Once the base client creates an MTS object, this object often creates additional MTS objects in the same server process. When you write the code for one MTS object to create another, you must do it properly or your new object might be placed in a different activity.
Let's look at the correct way to propagate new objects inside the MTS environment. When a base client creates an MTS object, the MTS run time creates it in a new activity. This object is known as the root of the activity. All MTS objects including the root should create additional MTS objects in the same activity by making activation requests through the MTS run time. You do this by calling the CreateInstance method on the object context. CreateInstance takes a single parameter for the ProgID of the new object. The following code demonstrates how to create a second MTS object in the same activity as the root:
' In a method of the root object Dim ObjCtx As ObjectContext Set ObjCtx = GetObjectContext() Dim Object2 As CMyClass2 Set Object2 = ObjCtx.CreateInstance("MyServer.CMyClass2")
When an object in the MTS environment calls CreateInstance, it tells the MTS run time to create the requested object in the same activity. The MTS run time creates the new object and places a context wrapper between it and its creator. You should note two important things here. First, since the two objects live in the same STA, there is no need for a proxy/stub layer between them. Second, the context wrapper is inserted between the two objects, so the MTS interception scheme is set up properly.
While you should create MTS objects from a Visual Basic base client using the CreateObject function and the New operator, you should be cautious when using these techniques in the code behind your MTS objects. Using them can result in an undesirable situation.
Let's examine what happens when you try to create one MTS object from another using CreateObject. When an MTS object calls CreateObject, the activation request bypasses the MTS run time and is sent down to the SCM. The SCM, in turn, calls back to the MTS run time with the same activation request. The MTS run time assumes that another base client is creating the object and therefore places it in a new activity. This means that the new object and its creator will probably run in different STAs. The two objects are bound together with a proxy/stub layer, which significantly degrades performance and consumes another STA from the thread pool unnecessarily.
You'll also get into trouble if you try to create one MTS object from another using the New operator. In one scenario, the creator component and the component from which the new object is created live in separate DLLs. In this case, a call to New is sent down to the SCM just like a call to CreateObject. This causes the new object to be created in a new activity. As you already know, this is undesirable. In the second scenario, in which the two components involved both live in the same ActiveX DLL, the problem is more subtle but can lead to even greater frustration. When a Visual Basic object calls New on a class name in the same DLL, the Visual Basic run time creates and binds the object on its own without involving either the MTS run time or the SCM. The new object isn't a valid MTS object because it doesn't have its own context wrapper. Your code won't crash, but it can exhibit unexpected behavior. When this invalid MTS object calls GetObjectContext, it's given a reference for the context object of its creator. This can lead to some strange and mysterious results.
As it turns out, sometimes you can and should use the New operator in a method implementation behind an MTS object. You can use the New operator when you create objects that aren't MTS objects. For example, you should use the New operator whenever you want to create ActiveX Data Objects (ADO). ADO components aren't registered with MTS. ADO objects don't need a context wrapper, and they don't use the object context.
You can thus conclude that an MTS application can run a mixture of MTS objects and standard objects. An MTS object requires a context wrapper, but a standard object doesn't. When you create a standard object from an MTS object using the New operator, it's created in the same activity, just like a call to CreateInstance. However, once a component has been programmed against the MTS type library and uses its object context, it must be registered with the MTS Explorer and properly created as an MTS object. As a rule of thumb, one MTS object must always create another MTS object using CreateInstance.