The singleton service is the ultimate sharable service. When you configure a service as a singleton, all clients are independently connected to the same single well-known instance context and implicitly to the same instance inside, 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.
Note
A singleton hosted in IIS or the WAS is created when you launch the host process (only when the first request to any service in that process is made). However, when using the auto-start feature of Windows Server AppFabric, the singleton will be instantiated once the machine starts up. To maintain the semantic of the singleton, use either self-hosting or Windows Server AppFabric with auto-start.
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 context and the instance inside. 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
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(); //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
.
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 = 388;
Note
The InProcFactory<T>
(presented in
Chapter 1) is similarly extended to
initialize a singleton instance.
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 if the singleton’s state is mutable and 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, 3rd 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.