问题
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
回答1:
You can cast callback contract to ICommunicationObject and then check for the channel state.
回答2:
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.
回答3:
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.
来源:https://stackoverflow.com/questions/1981113/wcf-duplex-channel-check-if-callback-channel-is-still-available