Context
In order for us to understand how the runtime is able to enforce a threading requirement based on an attribute, we have to introduce the concept of context. Step 4 of the Threading example is the same code as Step 3, but with some additional output:
Is the customer object a proxy? False Is the bookings object a proxy? True Added Boston Presidential Hotel with one room. Thread 13 ContextId 0 launching threads. MakeReservation: Thread 28 ContextId 0 starting. MakeReservation: Thread 29 ContextId 0 starting. Reserving for Customer 2 at the Boston Presidential Hotel on 12/13/2001 12:00:00 AM for 1 days Thread 29 ContextId 1 entered Reserve. Thread 29 finds the room is available in Broker::Reserve Reserving for Customer 1 at the Boston Presidential Hotel on 12/12/2001 12:00:00 AM for 3 days Thread 29 ContextId 1 left Reserve. Thread 28 ContextId 1 entered Reserve. Thread 28 ContextId 1 left Reserve. Reservation for Customer 1 could not be booked Room not available Reservation for Customer 2 has been booked ReservationId = 1 ReservationRate = 10000 ReservationCost = 10000 Comment = OK
In this last step (Step 4) of the Threading example, we see that when a thread enters a method, such as Reserve of the Broker class, it has a different ContextId than when it runs outside of the Broker class. It runs in a different context. That is why you see above that thread 28 has the ContextID set to 0 before it calls Reserve, and then within Reserve, its ContextID changes to 1. This is due to the Synchronization attribute being applied to the Broker class.
Broker objects have different runtime requirements than the other objects in the program, since access to Broker objects must be synchronized and access to other objects should not be synchronized. The environment that represents the runtime requirements of an object is called a context. There are two contexts in the Threading Step 3 and Step 4 examples: Context 1 where the Broker object lives and Context 0 where all other objects live. Every thread in the program runs in Context 1 when executing inside a Broker object, Context 0 everywhere else. Contexts are independent of threads.
A context is a collection of one or more objects that have identical concurrency requirements. The .NET concept of a context is similar to the COM concept of an apartment and the COM+ concept of a context.13 In general, you cannot say what the runtime must do in a given context because it depends on exactly what the runtime requirements are. A context that has transactional requirements requires different action than one that does not. Or, a context that has to maintain a REQUIRED synchronization requirement is different than one that has to maintain a REQUIRES_NEW synchronization requirement.
You can get the Context class instance that represents the current context from the static property Thread::CurrentContext. ContextId is a property of the Context class.
Proxies and Stubs
How does the runtime enforce the different requirements of different contexts? When an object that resides in another context (such as the HotelBroker object in the NewReservation instance), a pointer to a proxy object is returned instead of a pointer to the object itself. The actual object resides in its original, or home, context. The proxy is an object that represents the original object in a different context. The static method RemotingServices::IsTransparentProxy determines if an object reference points to a real object instance or a proxy. Look at the code in the main routine in Threading.h of the TestThreading project in Step 4:
bool bTrans; bTrans = RemotingServices::IsTransparentProxy( customers); Console::WriteLine( "Is the customer object a proxy? {0}", bTrans.ToString()); bTrans = RemotingServices::IsTransparentProxy( hotelBroker); Console::WriteLine( "Is the bookings object a proxy? {0}", bTrans.ToString());
This causes the following output:
Is the customer object a proxy? False Is the bookings object a proxy? True
When a program starts up, it is given a default context.14 All objects, like the Customers object, that do not have any special requirements are created inside of that context (context 0). An object, such as the HotelBroker object, that has a different set of requirements (synchronization) is created in a different context (context 1), and a proxy is returned to the creating context (context 0).
Now when you access the MakeReservation method in the HotelBroker object, you are actually accessing a method on the proxy. The proxy method can then apply the synchronization lock and then delegate to the actual HotelBroker object's method. When the actual object's method returns, it returns to the proxy. The proxy can then remove the synchronization lock and return to the caller. This technique, where the runtime uses a proxy to intercept method calls to the actual object, is called interception.
You may also want to look at a similar example named MarshalByReference in the example directory; it shows how an object can be created in different ways and the effect on whether or not a proxy object is created.
ContextBoundObject
The Broker class has to derive from the class ContextBoundObject so that the runtime knows to set up a different context if one is required. If you remove the derivation of Broker from ContextBoundObject, you will once again get the threading conflict, and both customers will be able to reserve the last room at the hotel, even though the class is still marked with the Synchronization attribute. Objects that do not derive from ContextBoundObject can run in any context (agile objects).
Since other contexts work with a proxy or a reference to the actual object, the runtime must translate (marshal) the call from one context to another. Hence, ContextBoundObject inherits from MarshalByRefObject. MarshalByRefObject is the base class for objects that need to be able to be marshaled by reference.
One advantage of using synchronization techniques such as a Monitor is that a Monitor can be called from any context. Another potential disadvantage of using automatic synchronization is the performance hit from marshaling and using proxies rather than the actual object.
As will be clear when we discuss application domains, since the customer object has no dependency on context, it is the actual object that is accessed, not a proxy. It can be copied to any context within the same application domain.