IDisposable and WCF

Recently we’ve been separating our monolithic application into smaller systems which communicate via services.  We’re using WCF for this communication, and one of the things that we’ve quickly noticed is that WCF is, for whatever reason, not compatible with the usual best practice of wrapping IDisposable objects with a using() {…} block.  Personally, I don’t think resources should be marked IDisposable if you can’t simply use the using() statement.  The issue with the case of WCF’s client’s is that the call to Close() may throw an exception (a network error).  I have to believe that other disposable resources might also run into problems when cleaning up their connections, but somehow they’re still compatible with using().

Here’s an MSDN article showing the problem quite well:

Avoiding Problems with the Using Statement

If you do go the using() route, and errors occur, they end up throwing from the closing brace of the using() block, and being rather difficult to diagnose.  Here’s an example of such a WCF error with a using() statement.

It also includes the code for how to clean up after services correctly in light of the fact that they may blow up due to network errors.  Here’s the code that you shouldn’t use because WCF doesn’t work with it:

using (var myClient = new ServiceClient())
{
  int value = myClient.GetSomeValue();
}

Here’s the code that you’re forced to use instead:

var myClient = new ServiceClient();
try
{
  int value = myClient.GetSomeValue();
  // ...
  myClient.Close();
}
catch (CommunicationException e)
{
  // ...
  client.Abort();
}
catch (TimeoutException e)
{
  // ...
  client.Abort();
}
catch (Exception e)
{
  // ...
  client.Abort();
  throw;
}

Isn’t that nice and clean?  Of course you can add it as an Extension Method to your ServiceClient.  If you do that, then you simply need to remember to use a finally block to call the code.  I’m not sure at that point that you’re any further ahead than if you just used the using() statement to begin with, but at least it eliminates the problem of having an exception thrown on the closing brace of the using block  and keeps the total amount of plumbing code that needs written to a minimum.  Your code then might look like this:

var myClient = new ServiceClient();
try
{
  int someValue = myClient.GetSomeValue();
}
finally
{
  myClient.CloseConnection(); // extension method
}

 

And here’s the extension method:

public static class Extensions
    {
        /// <summary>
        /// Safely closes a service client connection.
        /// </summary>
        /// <param name="myServiceClient">The client connection to close.</param>
        public static void CloseConnection(this ICommunicationObject myServiceClient)
        {
            if (myServiceClient.State != CommunicationState.Opened)
            {
                return;
            }
 
            try
            {
                myServiceClient.Close();
            }
            catch (CommunicationException ex)
            {
                Debug.Print(ex.ToString());
                myServiceClient.Abort();
            }
            catch (TimeoutException ex)
            {
                Debug.Print(ex.ToString());
                myServiceClient.Abort();
            }
            catch (Exception ex)
            {
                Debug.Print(ex.ToString());
                myServiceClient.Abort();
                throw;
            }
        }
    }

This method simply logs any exceptions via Debug.Print, but obviously you can adjust that to suit your exception handling procedures.

Note that if you *don’t* clean up these connections, they will come back to bite you.  One of our developers ran into an issue with our suite of integration tests for these services where they would work fine individually, but if you ran more than 10 of them, it would get real slow and start failing.  Turns out they weren’t being closed so after enough of them were opened, the rest would time out waiting for an open connection.  Yet another reason why testing pays off; I’m sure it would have been much harder for us to diagnose why our application was randomly slowing down and not working periodically (especially as this is for an unattended batch process).

blog comments powered by Disqus