views:

760

answers:

2

Hi guys.

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

A: 

There are events on a CommunicationObject (i.e. callback channel) for Closed and Faulted. You may want to add handlers for these and track which clients still have a valid channel available.

You can also take a look at the IChannelInitializer class to implement tracking of clients.

MattK
A: 

The main problem was that i didn't get any exceptions, except from a TimeoutException. Until the exception was fired my service was blocked for 1 min (it's the send-timeout I set).

I resolved the problem by making a 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 chat room 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 the has to notify all clients in a chat room, 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)
                        {
                            YagzClientAsyncCall<Message> 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.

Simon