Singleton Service

The singleton service is the ultimate sharable service. When a service is configured as a singleton, all clients are independently connected to the same single well-known instance, regardless of which endpoint of the service they connect to. The singleton is created exactly once, when the host is created, and lives forever: it is disposed of only when the host shuts down.

Tip

A singleton hosted in the WAS is created when the host process is launched (typically only when the first request to any service in that process is made).

Using a singleton does not require clients to maintain a logical session with the singleton instance, or to use a binding that supports a transport-level session. If the contract the client consumes has a session, during the call the singleton will have the same session ID as the client (binding permitting), but closing the client proxy will terminate only the transport session, not the singleton instance. If the singleton service supports contracts without a session, those contracts will not be per-call: they too will be connected to the same instance. By its very nature, the singleton is shared, and each client should simply create its own proxy or proxies to it.

You configure a singleton service by setting the InstanceContextMode property to InstanceContextMode.Single:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
class MySingleton : ...
{...}

Example 4-6 demonstrates a singleton service with two contracts, one that requires a session and one that does not. As you can see from the client call, the calls on the two endpoints were routed to the same instance, and closing the proxies did not terminate the singleton.

Example 4-6. A singleton service and client

///////////////////////// Service Code /////////////////////
[ServiceContract(SessionMode = SessionMode.Required)]
interface IMyContract
{
   [OperationContract]
   void MyMethod(  );
}
[ServiceContract(SessionMode = SessionMode.NotAllowed)]
interface IMyOtherContract
{
   [OperationContract]
   void MyOtherMethod(  );
}
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
class MySingleton : IMyContract,IMyOtherContract,IDisposable
{
   int m_Counter = 0;

   public MySingleton(  )
   {
      Trace.WriteLine("MySingleton.MySingleton(  )");
   }
   public void MyMethod(  )
   {
      m_Counter++;
      Trace.WriteLine("Counter = " + m_Counter);
   }
   public void MyOtherMethod(  )
   {
      m_Counter++;
      Trace.WriteLine("Counter = " + m_Counter);
   }
   public void Dispose(  )
   {
      Trace.WriteLine("Singleton.Dispose(  )");
   }
}
///////////////////////// Client Code /////////////////////
MyContractClient proxy1 = new MyContractClient(  );
proxy1.MyMethod(  );
proxy1.Close(  );

MyOtherContractClient proxy2 = new MyOtherContractClient(  );
proxy2.MyOtherMethod(  );
proxy2.Close(  );

//Output
MySingleton.MySingleton(  )
Counter = 1
Counter = 2

Initializing a Singleton

Sometimes, you may not want to create and initialize the singleton using just the default constructor. Perhaps initializing the state requires some custom steps or specific knowledge that the clients should not be bothered with, or that is not available to the clients. Whatever the reason, you may want to create the singleton using some other mechanism besides the WCF service host. To support such scenarios, WCF allows you to directly create the singleton instance beforehand using normal CLR instantiation, initialize it, and then open the host with that instance in mind as the singleton service. The ServiceHost class offers a dedicated constructor that accepts an object:

public class ServiceHost : ServiceHostBase,...
{
   public ServiceHost(object singletonInstance,params Uri[] baseAddresses);
   public object SingletonInstance
   {get;}
   //More members
}

Note that the object must be configured as a singleton. For instance, consider the code in Example 4-7. The class MySingleton will be first initialized and then hosted as a singleton.

Example 4-7. Initializing and hosting a singleton

//Service code
[ServiceContract]
interface IMyContract
{
   [OperationContract]
   void MyMethod(  );
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
class MySingleton : IMyContract
{
   public int Counter
   {get;set;}

   public void MyMethod(  )
   {
      Counter++;
      Trace.WriteLine("Counter = " + Counter);
   }
}
//Host code
MySingleton singleton = new MySingleton(  );
singleton.Counter = 287;

ServiceHost host = new ServiceHost(singleton);
host.Open(  );
//Do some blocking calls then
host.Close(  );

//Client code
MyContractClient proxy = new MyContractClient(  );
proxy.MyMethod(  );
proxy.Close(  );

//Output:
Counter = 288

If you do initialize and host a singleton this way, you may also want to be able to access it directly on the host side. WCF enables downstream objects to reach back into the singleton directly using the SingletonInstance property of ServiceHost. Any party on the call chain leading down from an operation call on the singleton can always access the host via the operation context's read-only Host property:

public sealed class OperationContext : ...
{
   public ServiceHostBase Host
   {get;}
   //More members
}

Once you have the singleton reference, you can interact with it directly:

ServiceHost host = OperationContext.Current.Host as ServiceHost;
Debug.Assert(host != null);
MySingleton singleton = host.SingletonInstance as MySingleton;
Debug.Assert(singleton != null);
singleton.Counter = 388;

If no singleton instance was provided to the host, SingletonInstance returns null.

Streamlining with ServiceHost<T>

The ServiceHost<T> class presented in Chapter 1 can be extended to offer type-safe singleton initialization and access:

public class ServiceHost<T> : ServiceHost
{
   public ServiceHost(T singleton,params Uri[] baseAddresses) :
                                                     base(singleton,baseAddresses)
   {}
   public virtual T Singleton
   {
      get
      {
         if(SingletonInstance == null)
         {
            return default(T);
         }
         return (T)SingletonInstance;
      }
   }
   //More members
}

The type parameter provides type-safe binding for the object used for construction:

MySingleton singleton = new MySingleton(  );
singleton.Counter = 287;

ServiceHost<MySingleton> host = new ServiceHost<MySingleton>(singleton);
host.Open(  );

and the object returned from the Singleton property:

ServiceHost<MySingleton> host = OperationContext.Current.Host
                                                      as ServiceHost<MySingleton>;
Debug.Assert(host != null);
host.Singleton.Counter = 287;

Tip

The InProcFactory<T> (presented in Chapter 1) is similarly extended to initialize a singleton instance.

Choosing a Singleton

The singleton service is the sworn enemy of scalability. The reason has to do with singleton state synchronization, rather than the cost of that single instance. Having a singleton implies that the singleton has some valuable state that you wish to share across multiple clients. The problem is that when multiple clients connect to the singleton, they may all do so concurrently, and the incoming client calls will be on multiple worker threads. The singleton must therefore synchronize access to its state to avoid state corruption. This in turn means that only one client at a time can access the singleton. This constraint may degrade throughput, responsiveness, and availability to the point that the singleton is unusable in a decent-sized system. For example, if an operation on a singleton takes one-tenth of a second, the singleton can service only 10 clients per second. If there are many more clients (say 20 or 100), the system's performance will be inadequate.

In general, you should use a singleton only if it maps well to a natural singleton in the application domain. A natural singleton is a resource that is, by its very nature, single and unique. Examples of natural singletons are a global logbook to which all services should log their activities, a single communication port, or a single mechanical motor. Avoid using a singleton if there is even the slightest chance that the business logic will allow more than one such service in the future (for example, adding another motor or a second communication port). The reason is clear: if your clients all depend on implicitly being connected to the well-known instance, and more than one service instance is available, the clients will suddenly need to have a way to bind to the correct instance. This can have severe implications for the application's programming model. Because of these limitations, I recommend that you avoid singletons in the general case and find ways to share the state of the singleton instead of the singleton instance itself. That said, there are cases when using a singleton is acceptable, as mentioned above.

Get Programming WCF Services, 2nd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.