Click here to Skip to main content
15,860,859 members
Articles / Programming Languages / C#

WCF Duplex Reentrant Services

Rate me:
Please Sign up or sign in to vote.
4.64/5 (12 votes)
25 Mar 2009CPOL7 min read 84.2K   2.9K   43   10
Shows a hands-on approach on how to implement Duplex Reentrant services in WCF.

Introduction

This article illustrates working with Duplex mode in WCF with concurrency set to Reentrant. In general, Duplex is one of the three message exchange patterns (MEPs) which are:

  • One-way
  • Request-Response (synchronous and asynchronous)
  • Duplex

On the other hand, concurrency in WCF services deals with how many concurrent threads can access these services at one time.

A full discussion of MEPs and Concurrency in WCF is not the intent of this article; as a matter of fact, that would be the job of a full book. As such, this article assumes familiarity (but necessarily experience) of the following:

  • WCF programming
  • MEPs in WCF
  • Concurrency modes in WCF
  • Basic concepts of threads

Scenario

The working example of this article simulates a scenario where a client application sends a request for a service to register for notifications about low temperature drops. The service later responds to the client in Duplex mode, meaning that the client won’t wait for the response, rather the service will inform the client of the temperature drop via a callback.

Building the Duplex Service

The service is self-hosted inside a Windows application. This is a duplex service using wsDualHttpBinding. The service code is shown below:

C#
[ServiceContract(CallbackContract=typeof(IClientCallback))]
public interface ITemperature
{
    [OperationContract(IsOneWay=true)]
    void RegisterForTempDrops();
}

public interface IClientCallback
{
    [OperationContract(IsOneWay = true)]
    void TempUpdate(double temp);
}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, 
                 ConcurrencyMode = ConcurrencyMode.Single)]
public class Temperature : ITemperature
{
    public void RegisterForTempDrops()
    {
        OperationContext ctxt = OperationContext.Current;
        IClientCallback callBack = ctxt.GetCallbackChannel<iclientcallback>();
        Thread.Sleep(3000); //simulate update happens somewhere;
                            //for example monitoring a database field
        callBack.TempUpdate(10);
    }
}

Let’s examine the code:

  • The “ITemperature” is the service contract and the “IClientCallback” is the callback interface for the client. The operation “RegisterForTempDrops” will be consumed by clients wanting to know about drops in temperatures; these clients, on the other hand, will have to implement the callback interface in order for the service to be able to call their “TempUpdate” method for notification. Note how both “RegisterForTempDrops” and “TempUpdate” are one-way operations.
  • The ServiceBehavior attribute defines a PerSession instance mode and a Single concurrency mode. These are the default WCF settings any way, but it is always a better practice to explicitly write down the code. At a glance, the PerSession instance mode means that each client will get its own service instance on the first call and that the same instance will keep serving the client until the proxy is explicitly closed or the session times out. Single concurrency mode, on the other hand, means that one thread at a time will be able to access the service instance; so, if the client is trying to issue multiple concurrent calls to the service instance which has been allocated to it, the calls will be queued and served one at a time because multithreading is disabled at the service (ConcurrencyMode.Single).
  • The “Temperature” class implements the “RegisterForTempDrops” method. This method calls back to the client and sends the notification via the “TempUpdate” method in Duplex mode.

The Duplex configuration of the service is shown below:

6.JPG

Here, we are using wsDualHttpBinding for duplex operations. Also exposed is the metadata exchange endpoint (MEX) which enables using the Add Service Reference option for the client via Visual Studio.

Finally, the code for starting the self-hosted service:

C#
internal static ServiceHost myServiceHost = null;
private void button1_Click(object sender, EventArgs e)
{
    myServiceHost = new ServiceHost(typeof(Temperature));
    myServiceHost.Open();
    MessageBox.Show("Service Started!");
}

private void button2_Click(object sender, EventArgs e)
{
    if (myServiceHost.State != CommunicationState.Closed)
    {
        myServiceHost.Close();
        MessageBox.Show("Service Stopped!");
    }
}

Building the Duplex Client

The client is a console application which consumes the service and gets the result back by implementing the callback interface. The first step is to add a service reference to the WCF service. First, you have to start the service by running the Windows application and clicking on the “Start” button. Next, at the client, use VS to add a service reference as follows (notice the use of the base address specified in the service configuration; this works because of the MEX endpoint):

1.JPG

The client code is shown below:

C#
public class CallBackHandler : ITemperatureCallback
{
    static InstanceContext site = new InstanceContext(new CallBackHandler());
    static TemperatureClient proxy = new TemperatureClient(site);

    public void TempUpdate(double temp)
    {
        Console.WriteLine("Temp dropped to {0}", temp);
    }

    class Program
    {
        static void Main(string[] args)
        {
            proxy.RegisterForTempDrops();

            Console.ReadLine();

        }
    }
}

Let’s examine the code:

  • For the client to be able to participate in a Duplex conversation, it needs to have its own endpoint for the service to callback to. This code is automatically generated using the Add Service Reference option.
  • Note how the client class “CallBackHandler” implements the “ITemperatureCallback” which is the type defined at the service; this will enable Duplex communication.
  • The “InstanceContext” holds context information about a service instance; the client uses this context to create the proxy. This is different than when consuming non-duplex services where the “InstanceContext” is not needed. In this case, the “InstanceContext” holds references about the client’s channels automatically created in order for the client to participate in a Duplex operation.
  • Using the proxy, the client calls the “RegisterForTempDrops” service method.
  • The client will then get the callback from the service on the method “TempUpdate”.

Run the Sample

With the service application running, create a new instance of the client console application, and notice how after three seconds (the time which simulates the temperature dropping event) the client application will display a message that it was notified, as shown below:

2.JPG

Sample Architecture

Now, let’s see a simple sketch of the architecture of what just happened. The image below shows the Duplex communication between the service and the client:

3.JPG

  1. The client consumes the One-Way operation “RegisterForTempDrops” of the service; no response comes back for one-way operations.
  2. The service processes the request and calls back to the client on the One-Way operation “TempUpdate”. The duplex communication has ended; it’s that simple!

However, recall that the service behavior was defined as Single concurrency mode; meaning that only one thread can access the service instance at a time. Ok, so now can you imagine what will happen if the method “TempUpdate” was not One-Way? So let’s say that the “TempUpdate” method should return a Boolean value to the service to indicate that it has successfully been notified. Let’s see the architecture diagram updated for this scenario:

4.JPG

  1. Again, the client calls for the one-way “RegisterForTempDrops” operation (thread A).
  2. Also, the service processes the request and calls for the method “TempUpdate” on the client.
  3. The big difference now is that “TempUpdate” is not a one-way operation anymore; instead, it returns a boolean value to the service. So now, the client is trying to call the service to deliver the return of the “TempUpdate” method. This will happen on another thread B. But, recall that thread A is still “hanging on” to the service instance which has its concurrency mode set to Single, so it cannot serve more than a single thread at a time. As a result, thread B will be stuck, and you will have a dead lock case where thread B is striving for a resource (the service instance); nonetheless, that same resource is being captured by thread A who is refusing to let go simply because in thread A’s perspective, the communication cycle is not finished yet.

Want to see this in action? Keep reading.

Modifying the Sample

In order to see the above scenario in action, change the callback operation and set its return type to bool as opposed to void and remove the One-Way mark. The service code now is shown below:

C#
[ServiceContract(CallbackContract=typeof(IClientCallback))]
public interface ITemperature
{
    [OperationContract(IsOneWay=true)]
    void RegisterForTempDrops();
}

public interface IClientCallback
{
    //[OperationContract(IsOneWay = true)]
    //void TempUpdate(double temp);

    [OperationContract]
    bool TempUpdate(double temp);
}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, 
                 ConcurrencyMode = ConcurrencyMode.Single)]
public class Temperature : ITemperature
{
    public void RegisterForTempDrops()
    {
        OperationContext ctxt = OperationContext.Current;
        IClientCallback callBack = ctxt.GetCallbackChannel<IClientCallback>();
        Thread.Sleep(3000); //simulate update happens somewhere; 
                            //for example monitoring a database field
        callBack.TempUpdate(10);
    }
}

Start the service and update the service reference at the client. The only change is to return a boolean value from the “TempUpdate” method. The client code is shown below:

C#
public class CallBackHandler : ITemperatureCallback
{
    static InstanceContext site = new InstanceContext(new CallBackHandler());
    static TemperatureClient proxy = new TemperatureClient(site);

    public bool TempUpdate(double temp)
    {
        Console.WriteLine("Temp dropped to {0}", temp);
        return true;
    }

    class Program
    {
        static void Main(string[] args)
        {
            proxy.RegisterForTempDrops();

            Console.ReadLine();

        }
    }
}

Now, run the client and notice that you won’t get any response back. You will actually enter a deadlock state. So, how do we solve this problem?

Reentrant Concurrency

The solution is actually ultra easy: just set the ConcurrencyMode value of the ServiceBehavior attribute to Reentrant instead of Single.

In order to understand the Reentrant mode, consider the image below:

5.JPG

When the client calls the service, the call thread (A) is marked with a marker which we will call “M”. Now, the service responds back to the client which processes the request, and sends the boolean result back to the service on thread (B), which is also marked with marker “M”. WCF checks if the thread coming to the service has the same marker of the first thread, then that means that the call coming in is in response to a call that went out, and as a result, the call is accepted.

So in summary, we are still doing single threading but with the additional feature of allowing this particular call to come back; otherwise, we will have a deadlock. That’s single threaded with Reentrant feature!

In order to see that in action, just change the ConcurrencyMode of the ServiceBehavior to Reentrant instead of Single. This is shown below:

C#
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, 
                 ConcurrencyMode = ConcurrencyMode.Reentrant)]

This time, the client invocation succeeds and the full cycle is done without any deadlocks.

Sample Program

You can download the service and client applications at the start of the article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
Lebanon Lebanon

Comments and Discussions

 
QuestionCan we use this sample in internet scenario? Pin
Vaibhav270420-Mar-15 20:34
Vaibhav270420-Mar-15 20:34 
GeneralExcellent explanation with very simple example Pin
S.S.Cheral12-Nov-13 20:08
S.S.Cheral12-Nov-13 20:08 
QuestionExcelent article, thanks for the clear and straightforward expanation which leaves no questions at all ! Pin
Member 101544072-Aug-13 16:27
Member 101544072-Aug-13 16:27 
QuestionHow we can work the same service in internet..? Pin
nikhildsoni200427-Jul-12 0:21
nikhildsoni200427-Jul-12 0:21 
GeneralMy vote of 5 Pin
Illutionist7-Sep-11 2:31
Illutionist7-Sep-11 2:31 
General5, for you Pin
ring_024-Mar-11 19:02
ring_024-Mar-11 19:02 
I can not explain how much this has helped me understand WCF.
cheers.
Man having 1 bit brain with parity error

GeneralMy vote of 5 Pin
IvanaD29-Jun-10 23:57
IvanaD29-Jun-10 23:57 
GeneralCall back without opening a port on client side Pin
Josh19911823-Aug-09 15:24
Josh19911823-Aug-09 15:24 
GeneralNice work, simple and easy to understand. Pin
ramuknavap1-Apr-09 14:33
ramuknavap1-Apr-09 14:33 
GeneralRe: Nice work, simple and easy to understand. Pin
mohamad halabi2-Apr-09 23:21
mohamad halabi2-Apr-09 23:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.