Cleaning up client channels
Most of us have our own style of writing code we’ve been practicing throughout our career. Using the “using” statement for deterministic resource cleanup is one of my must haves in my keywords bag. But unfortunately I cannot use this to clean up the client channels I use in WCF. For example, if I use the following piece of code I’m unwittingly opening up a place where my program would simply crash. Why?
using (IClientChannel client = (IClientChannel)cf.CreateChannel())
{
IFoo foo = (IFoo)client;
foo.Bar();
}
The above code merely means that the following line will be executed upon exiting the using block.
((IDisposable)client).Dispose()
And the answer to above “Why?” actually lies within the Dispose implementation in the IClientChannel implementation. If we reflector System.ServiceModel.Channels.ServiceChannel, one of the IClientChannel implementers in WCF, we can clearly see that its Dispose implementation calls Close method in its base class CommunicationObject. In the world of CommunicationObjects, Close does not necessarily mean that everything will be fine. Likewise, when we call Close, we are essentially telling WCF to gracefully close the client channel. However, the evil things could still happen. For example, soon after calling Close we might lose the network connectivity before the channel can successfully send the protocol level messages that are required to terminate the current session.
So the bottom line is Close() can throw. And we have to be aware of it and write our code in a way that it does not crash even if the Close throws. There can be two kinds of things that can happened if something goes wrong while communicating with the service. Namely they are timeout exceptions and communication exceptions (yes, communication exceptions can be further broken into exceptions that inherits the CommunicationException, such as FaultException). When these things happen we should call Abort() in our client channel object to bring it to Closed state and throw away. Consequently the above code can be written better using the try/catch/abort pattern as follows.
IFoo client = null;
try
{
client = cf.CreateChannel();
client.Bar();
((IClientChannel)client).Close();
}
catch (TimeoutException)
{
((IClientChannel)client).Abort();
}
catch (CommunicationException)
{
((IClientChannel)client).Abort();
}
This code is fine as far as we call client channel methods within the try block. As soon as we have something else going on, we cannot guarantee that the code will only throw exceptions of these two types. For example consider a modified version of above code snippet like this.
IFoo client = null;
try
{
client = cf.CreateChannel();
client.Bar();
int x = 0, y = 0, z = 0;
// do some math to yeild x and y
z = x / y;
((IClientChannel)client).Close();
}
catch (TimeoutException)
{
((IClientChannel)client).Abort();
}
catch (CommunicationException)
{
((IClientChannel)client).Abort();
}
What happens if Y yields 0 after the math operations? We will hit a DivideByZeroException and neither our call to Close method nor calls to Abort method gets executed. So in such cases I would still recommend using a helper method in the finalizer to shutdown the communication object as follows.
IFoo client = null;
try
{
client = cf.CreateChannel();
client.Bar();
int x = 0, y = 1, z = 0;
// do some work to figure out the x and y
z = x / y;
((IClientChannel)client).Close();
}
catch (TimeoutException)
{
((IClientChannel)client).Abort();
}
catch (CommunicationException)
{
((IClientChannel)client).Abort();
}
finally
{
TryCloseOrAbort((IClientChannel)client);
}
static void TryCloseOrAbort(IClientChannel client)
{
if (client != null)
{
// Since this is called from the finalizer we don't know where the code
// was executing just before the finalizer. Therefore we check whether
// the communication object is already closed before we progress.
if (client.State != CommunicationState.Closed)
{
try
{
client.Close();
Console.WriteLine("Channel is closed.");
}
catch
{
client.Abort();
Console.WriteLine("Channel is aborted.");
}
}
}
}