Saturday, May 16, 2009

Dispose() in WCF

InfoQ posted a news “The problems with WCF and the Using Block” about the disposing client resource in WCF and gave some solutions to this issue. I was looking for some material for this question, and wanted to give my summarization here.

Without doubting, a resource (a unmanaged resource especially) need to implement the IDisposable interface. It’s  a precondition to use the using statement to manage the resources. However, if some exceptions occured in the using block, the resource couldn’t be recyled normally. Some important resouces such as the connection would keep opening and occupy the port and channel if they weren’t disposed. So it would impact on the performance of the system.

The best practice Microsoft recommends is using try/catch/(finally) statement. It requires the developer invoke the Close() in try block, and use the Abort() in catch block. The news provided the biggest difference between the Close() and the Abort:

Close() take a Timeout and has an async version, and also Close() can throw Exceptions. Abort() conversely is not supposed to block (or throw any expected exceptions), and therefore doesn’t have a timeout or an async version.

In a short, Close() will dispose the resoucres friendly and Abort() will do ungracefully. Because Close() may throw some exceptions such as CommunicationException and TimeoutException, so the code snippet on the client side would be like this:

var myClient = new MyClient();

try

{

    //do something

    myClient.Close();

}

catch (CommunicationException)

{

    myClient.Abort();

}

catch (TimeoutException)

{

    myClient.Abort();

}

catch (Exception)

{

    myClient.Abort();

    throw;

}

It is necessary of catching an exception at the end of code lines, because we don’t know if Close() method throws some unexpected exceptions such as OutOfMemoryException. In the news of InfoQ, the author introduced a solution to solve this issue provided by Steve Smith. He encapsulates these tedious code with using extension method. The type it wants to extend is ICommunicationObject, because all client objects implement the ICommunicationObject interface. The code snippet Stevie Smith wrote is as below:

public static class Extensions

{

    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;

        }

    }

}

 

We can replace the Close() with CloseConnection() method to avoid writting the repeated and tedious try/catch/finally statement. The other nice way is to use lambda expression. Its usage looks like using syntax. It defines a static method which accepts a ICommunicationObject object parameter and Action delegate:

public class Util

{

    public static void Using<T>(T client, Action action)

        where T : ICommunicationObject

    {

        try

        {

            action(client);

            client.Close();

        }

        catch (CommunicationException)

        {

            client.Abort();

        }

        catch (TimeoutException)

        {

            client.Abort();

        }

        catch (Exception)

        {

            client.Abort();

            throw;

        }

    }

}

 

We can pass the client implementation as lambda expression to Using() method:

Util.Using(new MyClient(), client =>

{

    client.SomeWCFOperation();

    //Do something;

});

 

In fact, there is another way to solve this issue. We can define a custom ChannelFactory to implement the IDisposable interface and let it as a Wrapper class to ChannelFactory. It also encapsulates the try/catch/finally statement in the Close() method of the custom ChannelFactory:

public class MyChannelFactory : IDisposable

{

    private ChannelFactory m_innerFactory;

    public MyChannelFactory(ChannelFactory factory)

    {

        m_innerFactory = factory;

    }

    ~MyChannelFactory()

    {

        Dispose(false);

    }

    public void Close()

    {

        Close(TimeSpan.FromSeconds(10));

    }

    public void Close(TimeSpan span)

    {

        if (m_innerFactory != null)

        {

            if (m_innerFactory.State != CommunicationState.Opened)

            {

                return;

            }

            try

            {

                m_innerFactory.Close(span);

            }

            catch (CommunicationException)

            {

                m_innerFactory.Abort();

            }

            catch (TimeOutException)

            {

                m_innerFactory.Abort();

            }

            catch (Exception)

            {

                m_innerFactory.Abort();

                throw;

            }

        }

    }

    private void Dispose(booling disposing)

    {

        if (disposing)

        {

            Close();

        }

    }

    void IDisposable.Dispose()

    {

        Dispose(true);

        GC.SuppressFinalize(this);

    }

}

 

Anyway, the essence of these solutions is to encapsulate the repeated logic, and ensure the security, performance and stability of the system.

No comments: