Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming / threads

Make WCF Duplex Breeze and Add Fun to it.

4.76/5 (7 votes)
17 Jan 2014CPOL9 min read 35.8K   536  
A running example of a duplex channel in WCF services.

Introduction

WCF has been around for a while, therefore, lots have been written about it, and it can be used in many scenarios, out of which the duplex scenario that we chose for the topic.

What encouraged me to write this clarification is that I have seen so many articles about the same subject, many of them had real examples, but if you go read them you still find something is missing, none of them will give you a clear picture of how the duplex works and when many found difficulties in making it work.

MSDN also, gives a clear working example that demonstrates just one scenario but not any other cases that you might need in your work.

This article certainly will not be covering all aspects of duplex or even pitfalls, all I wish is that it helps you building your duplex solution and prompts you to search for specific questions about that topic.

What Duplex is good for:

It is so common in most of the WCF contract to have two way communication between clients and the service, any operation in the contract can have parameters to be sent to the service and it also may have a return value that is sent back from the service to the clients, and that is per
se a two way communication, so when you work on a line of business project, you seldom need to use the duplex pattern.

However, duplex enables you to add more powerful control on this bidirectional communication and make things funnier and more powerful.

To understand duplex you imagine two WCF services, each consider the other as a client, in that case the first service will be able to control the “Client” and the “Client” will be able to control the service in the same way.

That is useful in communicative solutions or mainly in interactive games.

However, I don’t encourage using duplex so easily and indifferently, not just because it is a bit complicated but also because of security concerns that are quite as double risky as the one way channel communication.

What you need in order to build your Duplex:

Define the services:

First of all and before you even think of any duplex project, you need most importantly to decide clearly what the two services are going to do, how do they look like and which one is the main service and which is the secondary.

Define the callback service

As we stated earlier, there are two services to be defined, the main service, that can be called (the service, the server, etc.) whilst the other service is called the callback service (the client, the secondary, etc.)

Set the behaviors:

Set the right behavior on both the main service implementation and the callback service implementation.

Binding:

You need to be keen to choose the right binding, not all bindings are good for duplex pattern, however, rest assured that NetTCPBinding and NetNamedPipeBinding do support Duplex pattern.

Additionally, if you want to choose http protocol , there is a WSDualHttpBinding that is specially for Duplex pattern.

Thread blocking problem

When you open a bidirectional channel or dual channel and use it to communicate, you mostly will face a problem that your application will freeze and stop to interact.

Microsoft example emphasizes the use of IsOneWay; assuming that would help solving the blocking problem, I think that it reduces the powerfulness of dual communication.

When using DualChannel in WindowsForm, Microsoft introduced a property in the callback behavior that is UseSynchronizationContext to be set to false.

A nice article discussed many trials to find a workaround for this problem, the only one that worked was to use a separate thread to call the service.

Brandon Cannaday in his article discloses something that is worth mentioning..

" I've read several things stating that all that's actually required is to set the IsOneWay property on the callback's OperationContractattribute. In theory, that's how it should work, since if that is true, WCF will not perform any locking when sending that data. Unfortunately, that doesn't work. You should still keep that property set though, since it does other things you really will want.
The other major piece of information I came across was theUseSynchronizationContext property on the callback implementation'sServiceBehavior attribute. This one sounded really promising. When set to false, the callback will not automatically synchronize with the UI thread. This seems to be the source of the deadlock anyway, so I thought this was definitely it, but again, no luck.

All-in-all it came down to a simple fix that required a lot of headache to find. I'm not totally happy with the outcome, so if anyone else out there experienced this problem and has found a real solution, please let me know."
http://tech.pro/tutorial/914/wcf-callbacks-hanging-wpf-applications

In our example we don’t face that problem because we are using that System.Threading.Timer, this timer is multithreading timer and it make calls in different threads.

If we use another type of timers System.Timers.Timer, that does not support multithreading, then we will face the same problem.


Valid questions to research


In the Service Contract, Is the session Mode required?

Yes, it is, Duplex does require Session to work.

In the Service Contract, do all operations in the contract need to be Oneway?

No, as you see in the example, they can be two way, even though

Most of the examples you will see online will have oneway = true, to avoid thread blocking issue.

In the Service Contract, Should I define the Callback contract?

Yes, that is a must for it to work.

In the ServiceCallback contract, should I add the [ServiceContract] Attribute.

You may add it if you like but usually you don’t have to, since the compiler will do that for you.

In the Callback Service, what is the Concurrency mode?

[ConcurrencyMode.Reentrant]  

is not a must in all cases; the current example will work with Single and  

Multiple too.

In the callback service, should I use the attribute UseSynchronizationContext = false?

It is not a must in all cases, it is useful when you are having troubles in thread blocking when you use you host your ServiceCallback in a Windows Form application or WPF.

In case it does not work, make sure to call the operation in a separate thread using ThreadPool or any other technique.

What are the supported bindings?

Namedpipesbinding does support dualchannel.

NetTCPBinding does support dualchannel too.

For transportation over http protocol you can use a specialized binding for that type of communication called WSDualHttpBinding.

Using the code

I tried to make the code as simple as I could.

The solution consists of 4 projects; let’s call it Messenger to imply the communicative nature of it.

The first project is MessengerContracts, in this solution you identify the pure contracts, no code should be here. (This is not a must, you can merge the implementation with the contracts in one project even do worse, but keeping them separate is such a good practice in SOA architecture principles).

In this project I identified all the contracts:

The IMessenger, that represents the main service, and the IMessengerCallback that represents the client contract of the secondary service.

In addition to that, we should define all other contracts like the needed DataContracts, in this case, I needed some data to be transferred between the server and the client they are identified as IMessengerMessage.

C#
  [ServiceContract(Name = "IMessenger", Namespace = "<a href="http://assilabdulrahim/messenger">http://assilabdulrahim/messenger", SessionMode=SessionMode.Required , CallbackContract = typeof(IMessengerCallback))]
   public interface IMessenger
   {
       [OperationContract(Action = "<a href="http://assilabdulrahim/messenger/pushMessage">http://assilabdulrahim/messenger/pushMessage", IsOneWay = false)]
       int PushMessage(MessengerMessage message);
       [OperationContract(Action = "<a href="http://assilabdulrahim/messenger/writeOnClient">http://assilabdulrahim/messenger/writeOnClient", IsOneWay = false)]
       int WriteOnClient(MessengerMessage message);
   }


public interface IMessengerCallback
   {
       [OperationContract(IsOneWay = true)]
       void PullMessage(MessengerMessage message);
       [OperationContract(IsOneWay = true)]
       void WriteOnServer();
   }




 [DataContract]
   public class MessengerMessage
   {
       [DataMember]
       public string Text { get { return string.Format("This message is being written by {0} of instance number {1}, using thread id {2} ", ProcessName, InstanceNumber, ThreadId); } private set { } }

       [DataMember]
       public string ProcessName { get; set; }

       [DataMember]
       public int ThreadId { get; set; }

       [DataMember]
       public string InstanceNumber { get; set; }
   }

The second project is the implementation where we implement the service contract  and the client console application.

So lets start with the implementation of the secondary service that is the callback service.

It implements  IMessengerCallback

 AddressFilterMode is used just in my case, you might not need it, I needed it when I used TCP protocol. 

AddressFilterMode.All: "Indicates a filter that matches on any address of an incoming message.

Using this value turns off the WCF address filter check. Any message, no matter what its WS-Adressing:To identity is accepted." 

 InstanceContextMode and ConcurrencyMode are for you to change them and play with them and see their impact. 

You notice the locking that I am making, the locking is important if you are using multiple threads, if you really care about the connotation of the colors you are using. 

C#
[ServiceBehavior(
        InstanceContextMode = InstanceContextMode.PerSession,
        ConcurrencyMode = ConcurrencyMode.Single,
        AddressFilterMode = AddressFilterMode.Any)]
    public class Messenger : IMessenger
    {
     
        private IMessengerCallback Callback
        {
            get
            {
                return OperationContext.Current.GetCallbackChannel<IMessengerCallback>();
            }
        }

        static object _synchObject = new object();

        [OperationBehavior]
        public int PushMessage(MessengerMessage message)
        {
            lock (_synchObject)
            {
                var tmp = Console.ForegroundColor;
                switch (message.InstanceNumber)
                {
                    case "1":
                        Console.ForegroundColor = ConsoleColor.Red;
                        break;
                    case "2":
                        Console.ForegroundColor = ConsoleColor.Blue;
                        break;
                    case "3":
                        Console.ForegroundColor = ConsoleColor.Yellow;
                        break;
                    default:
                        Console.ForegroundColor = ConsoleColor.White;
                        break;
                }
                Console.WriteLine(message.Text);
                Console.ForegroundColor = tmp;
            }
           
            var file = new FileInfo(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
            WriteOnClient(new MessengerMessage() { ProcessName = file.Name, InstanceNumber = "Server Singleton",ThreadId = Thread.CurrentThread.ManagedThreadId });
            return message.Text.Length;
        }


        [OperationBehavior]
        public int WriteOnClient(MessengerMessage message)
        {
            Callback.PullMessage(message);
            return message.Text.Length;
        }
    }

The third project is the Client application, it represents the client and therefore it should implement the callback service. 

I Duplex, there is the client application that must implement the callback contract, in our case IMessegnerCallback.

The documentation says:

"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." 

 See the references for more information about the attributes. 

C#
[CallbackBehavior(
  ConcurrencyMode = ConcurrencyMode.Reentrant,
  UseSynchronizationContext = false)]

public class MessengerCallBack : IMessengerCallback
{
    //Some peopl like to make it Disposable when they generate the proxy

    IMessenger _proxy = null;
    Timer _timer = null;
    string _instanceNumber;

    public MessengerCallBack(string clientNumber)
    {
        _proxy = DuplexChannelFactory<IMessenger>.CreateChannel(new InstanceContext(this), "DualChannel");
        _timer = new Timer(new TimerCallback((x) => { this.WriteOnServer(); }), null, 1000, 3000);
        _instanceNumber = clientNumber;
    }

    public void WriteOnServer()
    {
        var file = new FileInfo(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
        var result = _proxy.PushMessage(new MessengerMessage() { ProcessName = file.Name, InstanceNumber = _instanceNumber, ThreadId = Thread.CurrentThread.ManagedThreadId });
    }

    public void PullMessage(MessengerMessage message)
    {
        var tmp = Console.ForegroundColor;
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine( message.Text);
        Console.ForegroundColor = tmp;
    }
}

 And the client application, you can run as many instances of it. 

class Program
{
    static void Main(string[] args)
    {

        MessengerCallBack client = new MessengerCallBack(args[0]);
        client.PullMessage(new MessengerMessage()
        {
            ProcessName = Thread.CurrentThread.ManagedThreadId.ToString()
            ,
            InstanceNumber = args[0]
        });

        Console.ReadLine();
    }
} 


Finally, I have the Host Application which should host the service.

This should first run before any instance of the client.

 

C#
   static void Main(string[] args)
       {
           string baseAddress = "net.tcp://localhost:8000/Messenger/";
           using (var host = new ServiceHost(typeof(MessengerImpl.Messenger),new Uri(baseAddress)))
           {
               host.Open();
               Console.WriteLine("Service is hosted, has the following endpoints, Hit <ENTER> to terminate");
               host.Description.Endpoints.ToList().ForEach(x => Console.WriteLine(x.Address));
               Console.ReadLine();
           }
       } 

Now we have 2 console applications, one represents the client and the other does the server.

To add more fun and coloring to the result, after compiling the  

client, you make 4 shortcuts for it and pass these numbers as parameters

respectively (1,2,3 and 4)

As it is shown below

Image 1

 

Image 2


Now you can run as much instances of the client as you want, and watch the results.
Each client will use a timer to write on the server.

The server will write on each client.

Run client1 five times (for instance) and client 2 ten times (for instance) and watch the result on the server console.

It is too much fun to see how the server can control the clients and the clients can do the server as well.

These clients’ shortcuts and parameters have nothing to do with the Duplex pattern, they are just added for fun, as a way to add colors and show communication and make it clearer.

As you can see in the image below.

The colorful console is the serverHost, it is being written onto by each client. All instances of Client1 are writing in red and all instances of Client2 are writing in blue ..ect.

Pay a clear attention for the threads ids, don't get confused by threads of the client and the threads of the ServiceHost. 

Image 3

 Notice:

The current code expects a parameter to the client as explained using the shortcuts, if you don't pass a parameter, it will break up.

You may modify the code to check for the parameters. 

What is next: 

 Feel free to download the source and run it as described and see how the colors play.

Now you may try to change the source code and play with the attributes and see what is the impact of each.

There are lots of things to discover or learn, as to me, what mattered in my case was the thread.

You can use a single thread and let me know how you can get it to work using the other parameters like Reentrant, or SynchronizationContext of the CallbackBehavior or even IsOneWay of the OperationBehavior. 

 

References:

ConcurrencyMode Enumeration

AddressFilterMode Enumeration 

WCF Callbacks Hanging WPF Applications

License

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