Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / C#
Article

Using WCF Callback Services Throttling

Rate me:
Please Sign up or sign in to vote.
4.97/5 (29 votes)
2 Sep 2008CPOL8 min read 93.6K   2.8K   82   11
When using callbacks with transient subscribers, you must leave the channel open. This is how to manage and use service throttling to open up your service to more than 10 subscribers.

Introduction

I work in an environment that utilizes BizTalk to process messages from various sources and distribute to numerous send ports. Now, one could utilize BizTalk as an ESB; however, this poses some issues, mainly a new send port needs to be created for each new application which needs messages routed to it. That is, unless one could guarantee all developers would only peek into a single message queue and never dequeue a message. Honestly, that would lead to more problems than it solves, not to mention it would be terrible practice. This led me to using WCF Callback Services as a single entry point in our ESB (Enterprise Service Bus).

Background

Considering WCF (Windows Communication Foundation) supports callback contracts and thus Event Driven Architecture, one could easily create a send port in BizTalk to a message queue (MSMQ, in this case), then expose a WCF Event Driven Web Service which numerous clients could easily subscribe to and receive messages from. Along my journey, I came across a lot of questions and issues. This is the reason I’m writing this entry today, to hopefully stop some of you from wasting too much time looking.

Using the Code

Let’s start with the easy part, the service contracts. This includes the callback contract as well as operations to allow clients to subscribe and unsubscribe. I will use the most basic web service I can think of, as to not stray from the actual issues I came across.

C#
[ServiceContract(SessionMode=SessionMode.Required, 
    CallbackContract=typeof(IEventSystemCallback))] 

interface IEventSystem 
{ 
  [OperationContract(IsOneWay=true)] 
  void Subscribe(); 

  [OperationContract(IsOneWay = true)] 
  void Unsubscribe(); 
} 

interface IEventSystemCallback 
{ 
  [OperationContract(IsOneWay = true)] 
  void OnMessageReceived(string message); 
}

Now, this is really all it takes to generate an event driven web service in WCF; at least, as far as the contracts are concerned. If you notice, there are two operations which will be exposed through the service (Subscribe and Unsubscribe). Remember, this is stripped to its basics for demonstration purposes. In the actual ESB solution, I have an object which represents the actual events one would like to subscribe to as well as other details for tracking purposes.

The IEventSystemCallback interface is the callback contract, as you will notice in the Service Contract for the IEventSystem interface. Therefore, you do not need a service contract attribute on the IEventSystemCallback contract. One must only expose all of the methods (or events) the client should expect.

Now, let’s get into the hard stuff. We’ll start with the service itself. I’ll show you the entire service implementation, and then we’ll break it down.

C#
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)] 
internal sealed class EventService:IEventSystem 
{ 
  public delegate void CallbackDelegate<T>(T t); 
  public static CallbackDelegate<string> MessageReceived; 

#region IEventSystem Members 

  public void Subscribe() 
  { 
    IEventSystemCallback callback = 
      OperationContext.Current.GetCallbackChannel<IEventSystemCallback>(); 
    MessageReceived += callback.OnMessageReceived; 
    ICommunicationObject obj = (ICommunicationObject)callback; 

    obj.Closed += new EventHandler(EventService_Closed); 
    obj.Closing += new EventHandler(EventService_Closing); 
  } 

  void EventService_Closing(object sender, EventArgs e) 
  { 
    Console.WriteLine("Client Closing..."); 
  } 

  void EventService_Closed(object sender, EventArgs e) 
  { 
    MessageReceived -= ((IEventSystemCallback)sender).OnMessageReceived; 
    Console.WriteLine("Closed Client Removed!"); 
  } 

  public void Unsubscribe() 
  { 
    IEventSystemCallback callback = 
      OperationContext.Current.GetCallbackChannel<IEventSystemCallback>(); 
    MessageReceived -= callback.OnMessageReceived; 
  } 

#endregion 

  public static void SendMessage(string Message) 
  { 
    try 
    {
      MessageReceived(Message); 
    }
    catch (Exception ex) 
    { 
      Console.WriteLine(ex.ToString()); 
    } 
  } 

  public static void NotifyServiceStop() 
  { 
    SendMessage("Service Stopping..."); 
  } 
}

First, let’s start with the service itself:

C#
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
internal sealed class EventService:IEventSystem

Here, we have implemented the IEventSystem interface and added a service behavior attribute to set the InstanceContextMode. You can set the InstanceContextMode to PerCall, PerSession, or Single. We will go into this more in the Throttling section, later on. For now, just know I chose PerSession because the use of callback contracts requires a session, and I just wanted to make it easier for explaining throttling later.

*Note: InstanceContextMode.PerSession and SessionMode=SessionMode.Required have no relation. You could have any InstanceContextMode, with Required SessionMode, as we will see later.

C#
public delegate void CallbackDelegate<T>(T t); 
public static CallbackDelegate<string> MessageReceived;

One of the most difficult things to grasp about a Callback Service is where to store all of the subscribers. This is why I’ve created the static GenericDelegate. Normally, I would place all my delegates inside another namespace and file location, but for ease of demo, I put the delegate creation right here in the service class. You could also store your subscriber reference (or InstanceContexts, if you will) inside a static collection object (List<T>, ArrayList, Hashtable, etc.). I chose a delegate because it’s really easy to utilize, and it is also consistent with how an Event Service works. All of the Instance Contexts will be stored as the target of the InvocationList object when you wire up the events, as we will see here.

C#
public void Subscribe()
{
  IEventSystemCallback callback = 
    OperationContext.Current.GetCallbackChannel<IEventSystemCallback>();
  MessageReceived += callback.OnMessageReceived;
  ICommunicationObject obj = (ICommunicationObject)callback;
  obj.Closed += new EventHandler(EventService_Closed);
  obj.Closing += new EventHandler(EventService_Closing);
}

void EventService_Closing(object sender, EventArgs e)
{
  Console.WriteLine("Client Closing...");
}

void EventService_Closed(object sender, EventArgs e)
{
  MessageReceived -= ((IEventSystemCallback)sender).OnMessageReceived;
  Console.WriteLine("Closed Client Removed!");
}

public void Unsubscribe()
{
  IEventSystemCallback callback = 
    OperationContext.Current.GetCallbackChannel<IEventSystemCallback>();
  MessageReceived -= callback.OnMessageReceived;
}

The ClientChannel is passed by the subscriber automatically with every service method call. So, when they subscribe, we proceed to wire up all of the events utilized in this demo. It is really only necessary to wire up the OnMessageReceived method of the callback contract, but how do you handle disconnected clients? I’ve seen it done where the service just tries to monitor the channel state with every callback call, but why go through all the overhead when you can just wire up the Closing and /or Closed events of the ICommunicationObject (or IDuplexContextChannel, if you choose this cast)? There are also other useful events you can wire up on the channel, such as: Faulted, Opening, and Opened.

Lastly, I create a few methods to actually send the messages to the subscribers. With the actual ESB Service, This is where the MSMQ monitoring would occur and fire all the new messages to the subscribers.

C#
public static void SendMessage(string Message)
{
  try
  {
    MessageReceived(Message);
  }
  catch (Exception ex)
  {
    Console.WriteLine(ex.ToString());
  }
}

public static void NotifyServiceStop()
{
  SendMessage("Service Stopped!");
}

I also added a NotifyServiceStop method for the same reason that I wired up the subscriber's Closing and Closed events. It’s only nice to notify the subscribers that the service is going down. In my implementation, I have a ServiceNotification object to allow for multiple types of notifications to be sent, the same way I have the EventNotifications for multiple events to be transmitted.

I have hosted this demo in a console application using a ServiceHost. You can self host the same way using a Windows service, or utilize WAS in IIS 7 to host the netTcp Service. There are also a few other ways to accomplish this, but that’s outside the scope of this article.

C#
class Program
{
  private static int count;

  public static int Count
  {
    get
    {
      count += 1;
      return count;
    }
  }

  static void Main(string[] args)
  {
    ServiceHost host = new ServiceHost(typeof(EventService));
    host.Closing += new EventHandler(host_Closing);
    host.Open();
    Console.WriteLine("Service Started");
    Timer timer = new Timer(delegate(object state)
    {
      string message = "Message " + Count.ToString();
      try
      {
        if (EventService.MessageReceived != null)
        {
          Console.WriteLine(
            EventService.MessageReceived.GetInvocationList().Length.ToString() + 
            " Subscribers");
          EventService.SendMessage(message);
          Console.WriteLine("Sent: " + message);
        }
        else
        {
          Console.WriteLine("0 Subscribers");
          Console.WriteLine("Skipped: " + message);
        }
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.ToString());
      }
    }, null, 5000, 5000);

    Console.ReadKey(true);
    host.Close();
  }

  static void host_Closing(object sender, EventArgs e)
  {
    try
    {
      EventService.NotifyServiceStop();
    }
    catch (Exception ex)
    {
      Console.WriteLine(ex.ToString());
    }
  }
}

There’s really no magic here. Just your ordinary ServiceHost implementation. The only things to point out here are that I wired up the host.Closing event to notify the subscribers of the service closing, and that with the use of the static delegate for storing the subscribers, you can inspect different aspects of the InvocationList. Here, I just show how many subscribers are listening.

Onto the configuration …

This is where most of the confusion came for me, and in fact, there are still some questions I have that you might be able to help out answering.

XML
<?xml version="1.0" encoding="utf-8" ?> 
<configuration> 
 <system.serviceModel> 
  <behaviors> 
   <serviceBehaviors> 
     <behavior name="ExposeMexAndThrottleBehavior"> 
       <serviceMetadata httpGetEnabled="true" 
          httpGetUrl="http://localhost:1111/EventService/Mex"/> 
       <serviceThrottling maxConcurrentCalls="3" 
          maxConcurrentInstances="100" 
          maxConcurrentSessions="100"/> 
     </behavior> 
   </serviceBehaviors> 
  </behaviors> 
  <services> 
    <service name="ESBService.EventService" 
           behaviorConfiguration="ExposeMexAndThrottleBehavior"> 
       <endpoint address="net.tcp://localhost:9999/EventService/" 
           binding="netTcpBinding" 
           contract="ESBService.IEventSystem"/> 
       <endpoint address="http://localhost:1111/EventService/Mex" 
           binding="mexHttpBinding" 
           contract="IMetadataExchange"/> 
    </service> 
  </services> 
 </system.serviceModel> 
</configuration>

I won’t be going into how to create an endpoint or expose metadata, as there are numerous examples on the web. The interesting thing to look at in this config is the serviceThrottling. When working with my first WCF duplex services, this was the most confusing. In fact, I didn’t even think I needed it because I didn’t want to “Throttle” my service at all. I would start up my service and start connecting clients one after the other, but I would continue to get stuck at 10 maximum clients subscribed. I tried setting the MaxConnections property on the netTcpBinding, with no luck.

XML
<bindings> 
  <netTcpBinding> 
     <binding name="tcpSettings" maxConnections="50"/> 
  </netTcpBinding> 
</bindings>

This setting is still confusing to me actually, because no matter what I set it to, it has no effect on my service. I read that you can “Throttle” the service using either the serviceThrottling or the maxConnections property, and the service will utilize the lowest setting between the two. Doesn’t seem to make any difference at all what I set the MaxConnections to, so don’t even use it.

The solution was presented with the use of the serviceThrottling setting, which is actually misleading in my opinion. The word throttling to me means limiting the Instances, Sessions, and Calls, when actually they are already “Throttled”, by default. If you take out the serviceThrottling behavior and inspect the ServiceHost.Description.Behaviors while the service is running, you will not see a ServiceThrottlingBehavior. This led me to believe that serviceThrottling was not implemented, unless specified explicitly. Therefore, I kept going down the path that it must have something to do with my TcpBinding settings. Come to find out that even though you can’t see the throttling behavior on the service, it’s there, throttling your service to unrealistic limits. Perhaps, a better name for the serviceThrottling behavior could be serviceLimiting.

To describe what these throttling settings mean, let’s go into them now.

Remember that I used a PerSession InstanceContextMode. That affects how I set my throttling parameters.

XML
<serviceThrottling maxConcurrentCalls="3"
  maxConcurrentInstances="100"
  maxConcurrentSessions="100"/>

The maxConcurrentSessions is the most important here. This allows 100 subscribers to connect to my service, in this example. With each subscription, the service will create a new session, and therefore a new instance, thus maxConcurrentInstances is set the same. In the real world, you will set these settings based upon your hardware constraints and consumers expected plus room to grow.

The maxConcurrentCalls should be set between 1 and 3 percent of your maxConcurrentInstances. This is why mine is set to 3.

Default values when not specified (or no serviceThrottling behavior is created):

  • maxConcurrentCalls = 16
  • maxConcurrentSessions = 10
  • maxConcurrentInstances = Unlimited

Now, your throttling will be set differently if you use a PerCall or Single InstanceContextMode. I won’t go into each configuration, in this demo. If you need help with them, please feel free to post, and I'll try to get back to you quickly.

Client code:

C#
public partial class Form1 : Form,ESB.IEventSystemCallback
{
  public Form1()
  {
    InitializeComponent();
  }
  ESB.EventSystemClient client;

  private void Form1_Load(object sender, EventArgs e)
  {
    try
    {
      client = 
       new ESBClient.ESB.EventSystemClient(new InstanceContext(this));
      client.Subscribe();
    }
    catch (Exception ex)
    {
      listBox1.Items.Add(ex.ToString());
    }
  }

#region IEventSystemCallback Members

  public void OnMessageReceived(string message)
  {
    listBox1.Items.Add(message);
  }

#endregion

* To use the sample code, open the client and service solutions in two separate VS2008 IDEs. Run the Service, then use CTRL+F5 to open multiple clients. Open and close as many clients you want. You can also adjust the throttling settings, then rinse and repeat.

License

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


Written By
Architect
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralWell done! Pin
Ronnie Rios16-Oct-13 13:44
Ronnie Rios16-Oct-13 13:44 
GeneralVery Helpful and useful example... Pin
Assil13-Mar-13 16:24
professionalAssil13-Mar-13 16:24 
QuestionError at client side. Pin
Amit Developer6-Jul-11 0:23
Amit Developer6-Jul-11 0:23 
GeneralHTTP-Binding Pin
nihat.ozkan13-Oct-10 13:47
nihat.ozkan13-Oct-10 13:47 
QuestionGreat article but what about serialization overhead? Pin
nisbusLAIS4-Sep-08 1:12
nisbusLAIS4-Sep-08 1:12 
AnswerRe: Great article but what about serialization overhead? Pin
Fregate23-Sep-08 15:22
Fregate23-Sep-08 15:22 

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.