ConcurrencyMode Enumeration
Specifies whether a service class supports single-threaded or multi-threaded modes of operation.
Assembly: System.ServiceModel (in System.ServiceModel.dll)
| Member name | Description | |
|---|---|---|
| Multiple | The service instance is multi-threaded. No synchronization guarantees are made. Because other threads can change your service object at any time, you must handle synchronization and state consistency at all times. | |
| Reentrant | The service instance is single-threaded and accepts reentrant calls. The reentrant service accepts calls when you call another service; it is therefore your responsibility to leave your object state consistent before callouts and you must confirm that operation-local data is valid after callouts. Note that the service instance is unlocked only by calling another service over a WCF channel. In this case, the called service can reenter the first service via a callback. If the first service is not reentrant, the sequence of calls results in a deadlock. For details, see ConcurrencyMode. | |
| Single | The service instance is single-threaded and does not accept reentrant calls. If the InstanceContextMode property is Single, and additional messages arrive while the instance services a call, these messages must wait until the service is available or until the messages time out. |
ConcurrencyMode is used in conjunction with the ConcurrencyMode property to specify whether a service class supports single-threaded or multi-threaded modes of operation. A single-threaded operation can be either reentrant or non-reentrant.
The following table shows when Windows Communication Foundation (WCF) permits an operation to be invoked while another one is in progress, depending upon the ConcurrencyMode.
ConcurrencyMode Value | Can a new operation be invoked? |
|---|---|
Single | Never. |
Reentrant | Only while invoking another service or a callback. |
Multiple | Always. |
The following code example demonstrates the different between using Single, Reentrant, and Multiple. This sample does not compile without a real implementation behind it, but does demonstrate the kind of threading guarantees that WCF makes and what that means for your operation code.
using System; using System.ServiceModel; [ServiceContract] public interface IHttpFetcher { [OperationContract] string GetWebPage(string address); } // These classes have the invariant that: // this.slow.GetWebPage(this.cachedAddress) == this.cachedWebPage. // When you read cached values you can assume they are valid. When // you write the cached values, you must guarantee that they are valid. // With ConcurrencyMode.Single, WCF does not call again into the object // so long as the method is running. After the operation returns the object // can be called again, so you must make sure state is consistent before // returning. [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Single)] class SingleCachingHttpFetcher : IHttpFetcher { string cachedWebPage; string cachedAddress; readonly IHttpFetcher slow; public string GetWebPage(string address) { // <-- Can assume cache is valid. if (this.cachedAddress == address) { return this.cachedWebPage; } // <-- Cache is no longer valid because we are changing // one of the values. this.cachedAddress = address; string webPage = slow.GetWebPage(address); this.cachedWebPage = webPage; // <-- Cache is valid again here. return this.cachedWebPage; // <-- Must guarantee that the cache is valid because we are returning. } } // With ConcurrencyMode.Reentrant, WCF makes sure that only one // thread runs in your code at a time. However, when you call out on a // channel, the operation can get called again on another thread. Therefore // you must confirm that state is consistent both before channel calls and // before you return. [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)] class ReentrantCachingHttpFetcher : IHttpFetcher { string cachedWebPage; string cachedAddress; readonly SlowHttpFetcher slow; public ReentrantCachingHttpFetcher() { this.slow = new SlowHttpFetcher(); } public string GetWebPage(string address) { // <-- Can assume that cache is valid. if (this.cachedAddress == address) { return this.cachedWebPage; } // <-- Must guarantee that the cache is valid, because // the operation can be called again before we return. string webPage = slow.GetWebPage(address); // <-- Can assume cache is valid. // <-- Cache is no longer valid because we are changing // one of the values. this.cachedAddress = address; this.cachedWebPage = webPage; // <-- Cache is valid again here. return this.cachedWebPage; // <-- Must guarantee that cache is valid because we are returning. } } // With ConcurrencyMode.Multiple, threads can call an operation at any time. // It is your responsibility to guard your state with locks. If // you always guarantee you leave state consistent when you leave // the lock, you can assume it is valid when you enter the lock. [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)] class MultipleCachingHttpFetcher : IHttpFetcher { string cachedWebPage; string cachedAddress; readonly SlowHttpFetcher slow; readonly object ThisLock = new object(); public MultipleCachingHttpFetcher() { this.slow = new SlowHttpFetcher(); } public string GetWebPage(string address) { lock (this.ThisLock) { // <-- Can assume cache is valid. if (this.cachedAddress == address) { return this.cachedWebPage; // <-- Must guarantee that cache is valid because // the operation returns and releases the lock. } // <-- Must guarantee that cache is valid here because // the operation releases the lock. } string webPage = slow.GetWebPage(address); lock (this.ThisLock) { // <-- Can assume cache is valid. // <-- Cache is no longer valid because the operation // changes one of the values. this.cachedAddress = address; this.cachedWebPage = webPage; // <-- Cache is valid again here. // <-- Must guarantee that cache is valid because // the operation releases the lock. } return webPage; } }
Available since 3.0