前面提到了Client通过Proxy或者Channel将服务调用转发给Service Instance(服务实例). 接下来的一个问题是, 假如有多个Client同时调用服务, Client与Service Instance之间是多对多的关系, 还是多对一的关系, 亦或其他关系?
回答这个问题之前, 先看WCF中服务寄宿的结构
Each service host instance has zero or more contexts. The context is the innermost execution scope of the service instance.A context is associated with zero or one service instance, meaning it could also be
empty (i.e., not associated with any service instance). It is the combined work of the service host and the context that exposes a native CLR type as a service. After the message is passed through the channels, the host maps that message to a new or existing context (and the object instance inside) and lets it process the call.
WCF支持以下三种实例上下文模式1
2
3
4
5
6public enum InstanceContextMode
{
PerCall,//单调
PerSession, //会话, 默认
Single //单例
}
The enum is correctly called InstanceContextMode rather than InstanceMode because it actually controls the instantiation mode of the context hosting the instance, rather than that of the instance itself. By default, however, the instance and its context are treated as a single unit, so the enum does control the life of the instance as well
PerCall模式
PerCall字面上就是每一次服务调用. 不管服务调用是否来自相同的客户端, host这边总是创建一个全新的实例上下文来处理每一个请求.

- The client calls the proxy and the proxy forwards the call to the service.
- WCF creates a new context with a new service instance and calls the method on
it. - When the method call returns, if the object implements IDisposable, WCF calls
IDisposable.Dispose() on it. WCF then destroys the context. - The client calls the proxy and the proxy forwards the call to the service.
- WCF creates an object and calls the method on it.
PerCall模式的好处
In the classic client/server programming model, using languages such as C++ or C#, every client gets its own dedicated server object. The fundamental problem with this approach is that it doesn’t scale well. Imagine an application that has to serve many clients. Typically, these clients create the objects they need when the client application starts and dispose of them when the client application shuts down. What impedes
scalability with the client/server model is that the client applications can hold onto objects for long periods of time, while actually using them for only a fraction of that time. Those objects may hold expensive or scarce resources, such as database connections, communication ports, or files. If you allocate an object for each client, you will tie up such crucial and/or limited resources for long periods, and you will eventually run out of resources.
A better activation model is to allocate an object for a client only while a call is in progress from the client to the service. That way, you have to create and maintain in memory only as many objects as there are concurrent calls, not as many objects as there are outstanding clients. My personal experience indicates that in a typical Enterprise system, especially one that involves users, at most 1% of all clients make concurrent calls (in a high-load Enterprise system, that figure rises to 3%). Thus, if your system can concurrently sustain 100 expensive service instances, it can still typically serve as many as 10,000 outstanding clients. This is precisely the benefit the per-call instance activation mode offers
1 | ///////////////////////// Service Code ///////////////////// |
PerSesssion模式
All bindings support configuring the contract on the endpoint with Session
Mode.Allowed. When the SessionMode property is configured with this value, transport sessions are allowed, but not enforced. The exact resulting behavior is a product of the service configuration and the binding used.
If the service is configured for percall activation, it still behaves as per-call service,
Whenthe service is configured for per-session activation, it will behave as a per-session service only if the binding used maintains a transport-level session. For example, the BasicHttpBinding can never have a transport-level session, due to the connectionless nature of the HTTP protocol. The WSHttpBinding without Message security and without reliable messaging will also not maintain a transport-level session. In both of these cases, although the service is configured with InstanceContextMode.PerSession and the contract with SessionMode.Allowed, the service will behave as a percall service.
However, if you use the WSHttpBinding with Message security (its default configuration) or with reliable messaging, or if you use the NetTcpBinding or the NetNamedPipeBinding, the service will behave as a per-session service.
When designing a sessionful contract, I recommend explicitly using SessionMode.Required and not relying on the default of SessionMode.Allowed
SessionMode.NotAllowed disallows the use of a transport-level session, which precludes(排除) an application-level session. Regardless of the service configuration, when this value is used, the service will always behave as a per-call service.
WCF can maintain a logical session between a client and a particular service instance. When the client creates a new proxy to a service configured as a sessionful service, the client gets a new dedicated(专用的) service instance that is independent of all other instances of the same service.
That instance will typically remain in service until the client no longer needs it. This activation mode (sometimes also referred to as the privatesession mode) is very much like the classic client/server model: each private session uniquely binds a proxy and its set of client- and service-side channels to a particular service instance, or more specifically, to its context. It follows that a transport session is required for the private-session instantiation mode, as discussed later in this section.
Because the service instance remains in memory throughout the session, it can maintain state in memory, and the programming model is very much like that of the classic client/server model. Consequently, it suffers from the same scalability and transaction issues as the classic client/server model. A service configured for private sessions cannot typically support more than a few dozen (or perhaps up to one or two hundred) outstanding clients, due to the cost associated with each such dedicated service instance.
The client session is per service endpoint per proxy. If the client creates another proxy to the same or a different endpoint, that second proxy will be associated with a new instance and session.
1 | ///////////////////////// Service Code ///////////////////// |
Every session has a unique ID that both the client and the service can obtain. The session ID is largely in the form of a GUID, and it can be used for logging and diagnostics. The service can access the session ID via the operation call context, which is a set of properties (including the session ID) that are used for callbacks, message headers, transaction management, security, host access, and access to the object represent‐
ing the execution context itself.
Single模式
The singleton service is the ultimate shareable 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.
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.
1 | ///////////////////////// Service Code ///////////////////// |
PerCall, PerSession, Single之间怎么选择
- Per-Call: 最通用; 适用于无状态
- Per-Session: 经典的C/S模式, 需要保存一些会话数据
- Singleton: 日志服务
Per-Call与Per-Session的区别 (https://stackoverflow.com/questions/15104960/persession-vs-percall)
In my opinion, to take a decision consider these two points
- For going with InstanceContextMode.PerSession - If your users have some session values stored on the WCF service on the server.
- For going with InstanceContextMode.PerCall - If your users have nothing stored in session on the WCF service on the server i.e. WCF service requires No per user settings required to store in memory. Requires scalability.
Some points regarding When-And-Why,
InstanceContextMode.PerCall
- If your service is stateless and scalable, i.e. benefits are similar to HTTP as it is also stateless.
- If service has light-weight initialization code (or none at all).
- If your service is single threaded.
- Example scenario: For any 1000 client requests in a given time period in a PerCall situation, there will only be 100 objects instantiated for 100 active calls. Secondly if server were to crash then in PerCall situation the only errors that would occur would be to the 100 actual requests that were in progress (assuming fast failover). The other 900 clients could be routed to another server on their next call.
InstanceContextMode.PerSession
- If your service has to maintain some state between calls from the same client.
- If your service has light-weight initialization code (or none at all). Even though you are only getting a new instance for each client proxy, you still want to be careful about having expensive initialization code in a constructor.
- Example scenario: For any 1000 client requests in a given time period in a PerSessionsituation you may have 1000 objects instantiated on the server but only 100 are actually activein call at any moment. And thus instantiated PerSession objects could be a waste of resources and may impact the ability to serve requests under load. Secondly if server were to crash then in PerSession all 1000 clients who have a session on that server would lose their session and be unable to complete their work.
Reference links:
- MSDN - WCF Instancing, Concurrency, and Throttling
- SO - Per-Call vs Per-Session
- MSDN - Using Sessions in WCF context
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 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 decentsized 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 earlier.