Remote Object Models In .NET (Cont.)
The Server-activated singleton activation model provides a single, well-known object to all clients. Because the clients connect to a single, well-known object, .NET ignores the client calls to new (to create a new instance), even if the singleton object is not created yet. (The .NET runtime in the client app domain has no way knowing what goes on in the host app domain anyway.) The singleton is created when the first client tries to access it. Subsequent client calls to create new objects and access attempts are all channeled to the same singleton object. Listing 2 demonstrates these points: You can see from the trace output that the constructor is only called once on the first access attempt, and that obj2 is wired to the same object as obj1.
The server-activated singleton activation model provides a single, well-known object to all clients.
Because the singleton constructor is only called implicitly by .NET under the covers, a singleton object cannot have parameterized constructors. Parameterized constructors are banned also because of an important semantic characteristic of the singleton activation model: At any given point in time, all clients share the same state of the singleton object (see Figure 4).
If parameterized constructors were allowed then different clients could call them with different parameters and that would result in different states per client. If you try to create a singleton object using a parameterized constructor, .NET will throw an exception of type RemotingException.
Figure 4: With a server-activated singleton object, all clients share the same well-known object.
Using singleton objects
Singleton objects are the sworn enemy of scalability. There are only so many concurrent client calls a single object can sustain so you may choose to use them rarely. Make sure that the singleton will not be a hot spot for scalability and that your design will benefit from sharing the singleton's object state. In general, use a singleton object if it maps well to a true singleton in the application logic, such as a logbook that all components should log their activities to. Other examples include a single communication port, a single mechanical motor, etc. Avoid using a singleton if there is a chance that the business logic will allow more than one such object in the future, such as adding another motor, a second communication port, etc. The reason is clear: If your clients depend on implicitly being connected to the well-known object, and if more than one object is available, the clients would suddenly need to have a way to bind to the correct object. This could have severe implications on the application's programming model. Because of these limitations, I recommend that you avoid singletons in the general case and find ways of sharing the state of the singleton, instead of the singleton object itself. That said, there are cases where using a singleton is a good idea. For example, class factories are usually implemented as singletons.
Singleton object lifecycle
Once a singleton object is created it should live forever. That presents a problem to the .NET garbage collection mechanism?even if no client presently has a reference to the singleton object, the semantics of the singleton activation model stipulate that the singleton be kept alive so that future clients can connect to it and its state. .NET uses leasing to keep an object in a different process alive, but once the lease has expired, .NET will disconnect the singleton object from the remoting infrastructure and will eventually garbage collect it. When you work with singleton objects, you need to explicitly provide the singleton with a long enough (or even infinite) lease. A singleton object should not provide a deterministic mechanism to finalize its state, such as implementing IDisposable. If it is possible to deterministically dispose of a singleton object, it will present you with a problem: Once disposed of, the singleton object becomes useless. Furthermore, subsequent client attempts to access or create a new singleton will be channeled to the disposed object. A singleton by its very nature implies that it is acceptable to keep the object alive in memory for a long period of time, and therefore there is no need for deterministic finalization. A singleton object should only use a Finalize() method (the C# destructor).
Activation Models and Synchronization
In a distributed application, the hosting domain registers the objects with .NET that it is willing to expose as well as their activation modes. Each incoming client call into the host is serviced on a separate thread from the thread pool. That allows the host to serve remote client calls concurrently and maximize throughput. The question is, what effect does this have on the object synchronization requirements?
Client-activated objects and synchronization
When it comes to synchronization, client-activated objects work like classic client-server objects. If multiple clients share a reference to an object, and the clients can issue calls on multiple threads at the same time, then you must provide for synchronization to avoid corrupting the state of the object. It would be best if the locking were encapsulated in the component itself either by using synchronization domains or by using manual synchronization. The reason is clear: Any client-side locking (such as using the lock statement) will only lock the proxy, not the object itself. Another noteworthy point is thread affinity?because each incoming call can be on a different thread, the client-activated object should not make any assumption about the thread it is running on, and avoid mechanisms such as thread-relative static or thread local storage. This is true even if the object is always accessed by the same client, and that client runs always on the same thread.
Single-call objects and synchronization
In the case of a single-call object, object-state synchronization is not a problem because the object's state in memory exists only for the duration of that call and cannot be corrupted by other clients. However, synchronization is required when the objects store state between method calls. If you use a database then you have to explicitly lock the tables, or you can use transactions with the appropriate isolation level to lock the data. If you are using the file system then you need to prevent sharing the files you access while a call is in progress.
Singleton objects and synchronization
Unlike client-activated objects, the clients of a singleton object may not even be aware that they are sharing the same object, and may not take the necessary precautions to lock the object before access. As a result, you should enforce synchronization of a singleton object on the object side. You can use either synchronization domain or manual synchronization. Similar to a client-activated object, a singleton object must avoid thread-affinity.
Distributed applications have come a long way from the days of simple client-server over the wire. Developing modern distributed applications requires taking into account the scalability and throughput needs, and selecting the appropriate object model. In the past, programming models such as single-call objects required application server platforms such as COM+. .NET remoting natively offers developers a range of programming models that take into account the scalability and performance needs of the application, thus significantly simplifying the overall programming model. As long as your objects comply with the design guidelines of the respective object models described in this article, you should be well on your way to successfully developing and deploying a distributed .NET application.
COM supported singletons by allowing developers to provide a special class factory that always returned the same object. The COM singleton behaved very similar to .NET singleton. When using ATL, you designated a class as a singleton by replacing the default class factory macro with the singleton macro. The main difference between a COM singleton and a .NET singleton is that with .NET, the object becomes a singleton because the host registers it as such. Other hosts can register the same component type as single-call or client-activated object. With COM, the singleton was always a singleton.