WCF is all about sending and receiving messages. There are many settings that have an impact on how a WCF application will scale. The following describes how the settings for instancing modes and concurrency mode impact the scalability of a WCF receiving application, and when they make thread synchronization necessary in your code.

Instancing Background Info

When a receiving application receives a message, that message gets dispatched to an object’s instance method. The lifetime of this receiving object can be tied to one or more received messages. The WCF programming model allows developers and administrators to change this association through the InstanceContextMode instance property on the ServiceBehaviorAttribute type. The possible settings are:

Single: every received message is dispatched to the same object (a singleton)

PerCall (default): every received message is dispatched to a newly created object

PerSession: messages received within a session (usually a single sender) are dispatched to the same object

Shareable: messages received within a session (can be one or more senders) are dispatched to the same object

The following is a service contract and receiving type definition that sets the InstanceContextMode to Single:

[ServiceContract]

public interface ISomeContract

{

  [OperationContract(IsOneWay=true)]

  void SomeOperation(Int32 number);

}

 

[ServiceBehavior(

  InstanceContextMode=InstanceContextMode.Single)]

sealed class Receiver : ISomeContract {

 

  internal Receiver() {

    Console.WriteLine("the receiver has been created\n");

  }

 

  public void SomeOperation(Int32 number) {

    // Do work here

  }

}

As you may have guessed, this setting has a direct impact on the lifetime of the receiving object, and it impacts the way we can share receiver object state when processing messages.

Concurrency Background

Since WCF is an asynchronous messaging platform. It makes extensive use of asynchronous I/O, and as a result, each received message may be dispatched to a receiving object by different threads. This feature allows WCF to use the CPU efficiently, and as a result, allows WCF applications to scale. The WCF programming model allows developers and administrators to choose how threads are utilized when messages are dispatched through the ConcurrencyMode instance property on the ServiceBehaviorAttribute type. The possible settings are:

Single (default): only one thread may access the receiver object at a time

Multiple: multiple threads may access the receiver object concurrently

Reentrant: only one thread may access the receiver object at a time, but callbacks may re-enter that object on another thread (Useful when performing asynchronous I/O)

The following is a service contract and receiving type definition that sets the ConcurrencyMode to Multiple.

[ServiceContract]

public interface ISomeContract

{

  [OperationContract(IsOneWay=true)]

  void SomeOperation(Int32 number);

}

 

[ServiceBehavior(

  InstanceContextMode=InstanceContextMode.Single,

  ConcurrencyMode=ConcurrencyMode.Multiple)]

sealed class Receiver : ISomeContract {

 

  internal Receiver() {

    Console.WriteLine("the receiver has been created\n");

  }

 

  public void SomeOperation(Int32 number) {

    // Do work here (must consider multiple threads)

  }

}

When is synchronization necessary?

Both the InstanceContextMode and the ConcurrencyMode instance properties on the ServiceBehaviorAttribute type play a role in determining whether or not it is necessary to synchronize access to a receiver’s state.

Let’s first consider the default settings of InstanceContextMode=InstanceContextMode.PerCall and ConcurrencyMode=Single. These settings dictate that a new receiver object will be created for every received message that is properly formatted, and that a single thread will invoke the instance method on the receiver object. The following service contract and receiver type definition can be used to test how these settings influence message processing (Sender code omitted for clarity):

[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall,

                 ConcurrencyMode=ConcurrencyMode.Single)]

sealed class Receiver : ISomeContract {

  // member variable for race condition

  Int32 someNumber;

 

  internal Receiver() {

    Console.WriteLine("a receiver has been created");

  }

 

  static void Main(string[] args){

    // create the address and binding

    Uri address = new Uri("net.tcp://localhost:4000/ISomeContract");

    NetTcpBinding binding = new NetTcpBinding(SecurityMode.None, false);

 

    // create the service host and add an endpoint

    Console.WriteLine("Creating ServiceHost");

    ServiceHost svc = new ServiceHost(typeof(Receiver));

    svc.AddServiceEndpoint(typeof(ISomeContract), binding, address);

 

    // show the concurrency mode

    ServiceBehaviorAttribute annotation = svc.Description.Behaviors.Find<ServiceBehaviorAttribute>();

    Console.WriteLine("important behaviors:");

    Console.WriteLine("\tconcurrency mode = {0}", annotation.ConcurrencyMode);

    Console.WriteLine("\tinstance context mode = {0}", annotation.InstanceContextMode);

    // open the service host (Start listening, among other things)

    Console.WriteLine("opening ServiceHost");

    svc.Open();

    Console.WriteLine("the Receiver is ready\n");

 

    // wait before exiting process

    Console.ReadLine();

 

  }

 

  // method can only be used with Concurrency Mode = PerCall

  public void SomeOperation(Int32 number) {

    // set the field value to the parameter and wait

    someNumber = number;

 

    Random rand = new Random();

    Thread.Sleep(rand.Next(1000));

    // show the current value of the field and parameter

    // with percall, they will always be the same

    Console.WriteLine("parameter = {0}, somenumber = {1}, thread = {2}\n",

      number.ToString(),

      someNumber.ToString(),

      Thread.CurrentThread.ManagedThreadId);

  }

}

If a sending application sends 5 messages to this receiving application, the SomeOperation method is invoked 5 times synchronously by the same thread. The following is the console output:

 

Since one and only one thread can interact with the receiver object at a time, and each message is dispatched to a new receiver object, there is no need write thread synchronization code in our receiver type definition.

If we change the InstanceContextMode to Single, we see the following output:

Here we have a single instance of the receiver type, but since the ConcurrencyMode is still Single, there is no need to write thread synchronization code in the SomeOperation method.

If, however, we change the ConcurrencyMode to Multiple, we see the following:

Clearly, we now have a race condition, and as a result, we have to write synchronization code in our SomeOperation method. While this can be quite a chore, allows us to service received messages more efficiently than if we allow only one thread at a time to work with the receiver object.

Repeating this process for the different settings of InstanceContextMode and ConcurrencyMode, the following grid becomes apparent (the non-shaded cells indicate whether or not synchronization code is necessary):

InstanceContextMode

ConcurrencyMode

Single (Default)

Multiple

Reentrant

Single

No

Yes

Yes

Shareable

No

Yes

Yes

PerSession

No

Yes

Yes

PerCall (Default)

No

No

No

Some thoughts on the table above…

1) Reentrant seems goofy. I don't see the compelling reason to use it (it is like single, but callbacks must be synchronized).

2) Single seems like a huge scalability limiter, and I don't think it should be used.

3) ConcurrencyMode should be set to Multiple. In my view, if a receiving application is going to scale, it must set concurrency mode to Multiple. If single or Reentrant are used, messages stack up as they wait to be processed, and that is badness.