Click here to Skip to main content
15,884,629 members
Articles / Programming Languages / C#
Tip/Trick

Detecting Client Connection in WCF Long Running Service (Heartbeat Implementation)

Rate me:
Please Sign up or sign in to vote.
5.00/5 (12 votes)
30 Sep 2014CPOL3 min read 27.2K   441   22   3
Heartbeat Implementation in WCF Service in C#

Introduction

Hello everyone! This is my first blog on WCF and I hope that you like it.

Today, I will talk about how to implement heart beat mechanism in WCF, so that whenever client is not alive, our WCF service detects it and kills its long running process and saves the server resources for other clients.

Background

Basic knowledge of WCF is required.

Using the Code

IMHO, I think there is no easy way to detect client has been closed or client is abruptly killed somehow.

Now, I am presenting one scenario and the problem in it and at last, the solution for it.

Scenario

Suppose there is one service which takes lots of time to complete, say 1 hour. And in-between, the client of WCF service gets killed.

Problem

Then the WCF service will continue to run till the whole method completes its execution, it means full 1 hour. WCF service does not have mechanism to stop its execution when client is no longer active or alive. It will waste huge server resource and will keep server busy in case resource is scarce on server side.

Solution

Suppose we have a method called “DoWork” that will take 1 hour to complete its execution.

C#
[ServiceContract]
 public interface ILongRunningService
 {
     [OperationContract]
     Output DoWork();
  }

Now to resolve the problem statement, we have to refactor this method into the below code snippet.

C#
[ServiceContract]
    public interface ILongRunningServiceV2
    {
        [OperationContract]
        void StartDoWork();

        [OperationContract]
        Status ContinueWorkAndGetStatusFromServer();

        [OperationContract]
        Output GetFinalDoWork();
    }

Now the method “DoWork” is divided into two parts, one is “StartDoWork” and the second is “GetFinalDoWork”.

The client will now call the first method “StartDoWork” and return immediately, “StartDoWork” will spawn a thread for its execution.

Then client will call “ContinueWorkAndGetStatusFromServer” continuously in while loop with wait (Thread.Sleep) of 1 second. This is required because it will keep the server informed that client is still connected to WCF Service and “DoWork” method execution should not stop. In case client does not call this method “ContinueWorkAndGetStatusFromServer”, then service after few elapsed seconds will stop the execution of “DoWork”.

In case client keeps calling this method and method “DoWork” completes its execution, then the client will call “GetFinalDoWork” to fetch the result from the service.

This implementation is also called heart beat implementation.

Now you can see the code snippet below and download the source code.

And this is the original implementation of method “DoWork”.

C#
public class LongRunningService : ILongRunningService
   {
       public Output DoWork()
       {
           var builder = new StringBuilder();
           for (int count = 0; count < 10; count++)
           {
               builder.Append("Count: " + count);
               Thread.Sleep(1000);
           }
           return new Output(builder.ToString());
       }
   }

Now, we have created one SessionManger class for managing the session of active client.

Whenever any client connects to our service, it will go through the SessionManager class which will maintain the object of ServiceProvider.

ServiceProvider is our new refactored implementation of method “DoWork”.

C#
public class ServiceProvider
   {
       private Output _resourceOutput;
       private readonly Status _status;
       private volatile bool _shouldDisconnect = false;
       private Timer _timer;
       private DateTime _clientDateTime;

       public ServiceProvider()
       {
           this._status = new Status()
           {
               Message = "Initialized",
               OperationStatus = OperationStatus.None
           };
       }

       public void StartDoWork()
       {
           TimerCallback timerCallback = StopActivity;
           _clientDateTime = DateTime.Now;
           _timer = new Timer(timerCallback, null, 2000, 2000);
           Task.Factory.StartNew(this.RunActivity);
       }

       public Status ContinueProcessingAndGetStatus()
       {
           _clientDateTime = DateTime.Now;
           return this._status;
       }

       public Output GetFinalDoWork()
       {
           if (this._status.OperationStatus == OperationStatus.Ready)
           {
               return this._resourceOutput;
           }
           else
           {
               throw new Exception("Exception");
           }
       }

       private void RunActivity()
       {
           this._status.OperationStatus = OperationStatus.Working;

           var builder = new StringBuilder();

           for (int count = 0; count < 10; count++)
           {
               if (_shouldDisconnect)
                   break;
               builder.Append("Count: " + count);
               Thread.Sleep(1000);
               Debug.WriteLine(count);
               this._status.Message = count.ToString();
           }

           this._status.OperationStatus = OperationStatus.Ready;
           _resourceOutput = new Output(builder.ToString());
       }

       private void StopActivity(object state)
       {
           TimeSpan span = DateTime.Now - _clientDateTime;
           if (span > new TimeSpan(0, 0, 7))
           {
               this._timer.Dispose();
               _shouldDisconnect = true;
           }
       }
   }

This is a SessionManager class.

C#
public static class SessionManager<TSessionHandler> where TSessionHandler
    : new()
{
    private static readonly ConcurrentDictionary<string, TSessionHandler>
        SessionHandlers = new ConcurrentDictionary<string, TSessionHandler>();

    private static readonly object SyncObject = new object();

    public static TSessionHandler GetSession()
    {
        var sessionId = OperationContext.Current.SessionId;
        if (SessionHandlers.ContainsKey(sessionId))
        {
            return SessionHandlers[sessionId];
        }
        else
        {
            lock (SyncObject)
            {
                TSessionHandler handler = new TSessionHandler();
                SessionHandlers.TryAdd(sessionId, handler);
                return handler;
            }
        }
    }
}

This is client code, the method “WithNormalService” is an old way of calling and method “WithVersion2Service” is a new way of calling service.

C#
static void WithNormalService()
    {
        LongRunningServiceClient client = new LongRunningServiceClient();
        var result = client.DoWork();
        Console.WriteLine(result.FinalValue);
    }

    static void WithVersion2Service()
    {
        LongRunningServiceV2Client client = new LongRunningServiceV2Client();
        client.StartDoWork();
        string serviceMsg = string.Empty;
        Status status = client.ContinueWorkAndGetStatusFromServer();

        Console.WriteLine(status.OperationStatus);
        do
        {
            if (!string.IsNullOrEmpty(status.Message))
            {
                if (!serviceMsg.Equals(status.Message))
                {
                    serviceMsg = status.Message;
                    Console.Out.WriteLine(status.Message);
                }
            }

            Thread.Sleep(500);

            status = client.ContinueWorkAndGetStatusFromServer();

        } while (status.OperationStatus != OperationStatus.Ready);

        ServiceReferenceV2.Output image = client.GetFinalDoWork();

        Console.WriteLine(image.FinalValue);
    }

Earlier, this was the flow of calling between client and service.

Client ---> DoWork

Now after the implementation of Heartbeat in WCF Service, this is the new way of calling WCF Service.

Client ---> StartDoWork

Client ---> ContinueWorkAndGetStatusFromServer in a while loop till it get Operation Status Ready, then it will call GetFinalDoWork.

Client ---> GetFinalDoWork

Point of Interest

One improvement point in this code is to remove the dead client references from the dictionary in SessionManager class.

History

  • 30th September, 2014 – First draft version

License

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


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

Comments and Discussions

 
QuestionNice and clear. Pin
igupta050416-Oct-14 0:22
igupta050416-Oct-14 0:22 
GeneralMy vote of 5 Pin
Marcel Kalinowski1-Oct-14 20:54
Marcel Kalinowski1-Oct-14 20:54 
GeneralMy vote of 5 Pin
Humayun Kabir Mamun30-Sep-14 20:23
Humayun Kabir Mamun30-Sep-14 20:23 

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.