Search code examples
wcfclientduplex-channel

WCF Duplex Channel: Check if callback channel is still available


I have the following problem. I'm writing chat software. The client/server mechanism is based on DualHttpBinding of WCF. This means that if a user sends a message, all clients that are in the room where the message has been sent, are notified by the server.

I want to ensure, that if a client's application crashes (whyever), the client object is removed from the rooms' lists.

Is there a possibility to check the callback channel's state before calling a callback operation? The problem is, that if i call an operation on a client which is not anymore connected (because of an unexpected crash), the service will hang.

 public YagzResult SendMessage(Message message)
    {
        foreach (ChatNodeAddress chatNodeAddress in message.Destination)
        {
            ChatNode chatNode = chatProvider.FindChatNode(chatNodeAddress);
            if (chatNode != null)
            {
                User currentUser = CurrentUser;
                foreach (User user in chatNode)
                {
                    //Don't notify the current client. Deadlock!
                    if (!user.Equals(currentUser))
                    {
                        //Get the callback channel here
                        IYagzClient client = GetClientByUser(user);

                        if (client != null)
                        {
                            //--> If the client here called is not any more available,
                            //the service will hang <---
                            client.OnChatMessageReceived(message);
                        }
                    }
                }
            }
            else
            {
                return YagzResult.ChatNodeNotFound;
            }
        }
        return YagzResult.Ok;
    }

How can i check if a client is still listening? BTW, the operations called on the client are all declared OneWay and the ConcurrencyMode is set to "Multiple".

Thank you all!

Greets,

Simon


Solution

  • The main problem was that I didn't get any exceptions, except from a TimeoutException. My service was blocked for 1 min (the timeout I set), until the exception was fired.

    I resolved this problem through the following workaround. Instead of calling the client callback operation on the current working thread of the service, I created a new thread that calls the client callback operation and waits for a TimeoutException. If the timeout occurs, the user is simply removed from the chatroom lists he belonged to.

    This is a code snippet that shows how I did it:

    At first I created a class representing a single call to the client:

    class YagzClientAsyncCall<T>
    {
        /// <summary> Gets or sets the parameter of the client callback. </summary>
        /// <value> The parameter. </value>
        T Param { get; set; }
    
        /// <summary> Gets or sets the client. </summary>
        /// <value> The client. </value>
        IYagzClient Client { get; set; }
    
        /// <summary> Gets or sets the service. </summary>
        /// <value> The service. </value>
        YagzService Service { get; set; }
    
        /// <summary> Constructor. </summary>
        /// <remarks> Simon, 30.12.2009. </remarks>
        /// <param name="service"> The service. </param>
        /// <param name="client">  The client. </param>
        /// <param name="param">   The parameter. </param>
        public YagzClientAsyncCall(YagzService service, IYagzClient client, T param)
        {
            Param = param;
            Client = client;
        }
    
        /// <summary>   
        /// Invokes the client callback. If a timeout exception occurs, 
        /// the client will be removed from clients' list.
        /// </summary>
        /// <remarks> Simon, 30.12.2009. </remarks>
        /// <param name="clientCallback">   The client callback. </param>
        protected void Invoke(Action<T> clientCallback)
        {
            try
            {
                if (clientCallback != null)
                {
                    clientCallback(Param);
                }
            }
            catch (TimeoutException)
            {
                // Remove the client and the user
                Service.RemoveClient(Client);
            }
        }
    
        protected void Invoke(object objCallback)
        {
            Invoke(objCallback as Action<T>);
        }
    
        public void CallOperationAsync(Action<T> clientCallback)
        {
            ParameterizedThreadStart ts = new ParameterizedThreadStart(this.Invoke);
            Thread t = new Thread(ts);
            t.Start(clientCallback);
        }
    }
    

    Suppose the following code is part of a method that notifies chatroom clients that a new message was written:

    foreach (User user in chatNode)
    {
         // Don't notify the current client. Deadlock!
         if (!user.Equals(currentUser))
         {
             IYagzClient client = GetClientByUser(user);
    
             if (client != null)
             {
                 var asyncCall = new YagzClientAsyncCall<Message>(this, client, message);
                 asyncCall.CallOperationAsync(client.OnChatMessageReceived);
             }
         }
     }
    

    I just create a new YagzClientAsyncCall-Object and let the operation be called on a new thread.