Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WCF using, closing and extensions

Tags:

c#

wcf

I am stumped. Perhaps someone can shed some light on WCF client behavior I am observing.

Using the WCF samples, I've started playing with different approaches to WCF client/server communication. While executing 1M of test requests in parallel, I was using SysInternals TcpView to monitor open ports. Now, there are at least 4 different ways to call the client:

  1. Create the client, do your thing, and let GC collect it
  2. Create the client in a using block, than do your thing
  3. Create the client channel from factory in a using block, than do your thing
  4. Create the client or channel, but use WCF Extensions to do your thing

Now, to my knowledge, only options 2-4, explicitly call client.Close(). During their execution I see a lot of ports left in the TIME_WAIT state. I'd expect option 1 to be the worst case scenario, due to reliance on the GC. However, to my surprise, it seems to be the cleanest of them all, meaning, it leaves no lingering ports behind.

What am I missing?

UPDATE: Source code

    private static void RunClientWorse(ConcurrentBag<double> cb)
    {
        var client = new CalculatorClient();
        client.Endpoint.Address = new EndpointAddress("net.tcp://localhost:8000/ServiceModelSamples/service");
        RunClientCommon(cb, client);                        
    }

    private static void RunClientBetter(ConcurrentBag<double> cb)
    {
        using (var client = new CalculatorClient())
        {
            client.Endpoint.Address = new EndpointAddress("net.tcp://localhost:8000/ServiceModelSamples/service");
            RunClientCommon(cb, client);
        }
    }

    private static void RunClientBest(ConcurrentBag<double> cb)
    {
        const string Uri = "net.tcp://localhost:8000/ServiceModelSamples/service";
        var address = new EndpointAddress(Uri);
        //var binding = new NetTcpBinding("netTcpBinding_ICalculator");
        using (var factory = new ChannelFactory<ICalculator>("netTcpBinding_ICalculator",address))
        {
            ICalculator client = factory.CreateChannel();
            ((IContextChannel)client).OperationTimeout = TimeSpan.FromSeconds(60);
            RunClientCommon(cb, client);
        }
    }

    private static void RunClientBestExt(ConcurrentBag<double> cb)
    {
        const string Uri = "net.tcp://localhost:8000/ServiceModelSamples/service";
        var address = new EndpointAddress(Uri);
        //var binding = new NetTcpBinding("netTcpBinding_ICalculator");
        new ChannelFactory<ICalculator>("netTcpBinding_ICalculator", address).Using(
            factory =>
                {
                    ICalculator client = factory.CreateChannel();
                    ((IContextChannel)client).OperationTimeout = TimeSpan.FromSeconds(60);
                    RunClientCommon(cb, client);
                });
    }
like image 206
Darek Avatar asked Nov 12 '22 05:11

Darek


1 Answers

I have figured it out, I think. The GC will not call Dispose on ClientBase. That's why the connections are not left in a TIME_WAIT state. So I decided to follow the same pattern and created a new WCF Extension:

    public static void UsingAbort<T>(this T client, Action<T> work)
        where T : ICommunicationObject
    {
        try
        {
            work(client);
            client.Abort();
        }
        catch (CommunicationException e)
        {
            Logger.Warn(e);
            client.Abort();
        }
        catch (TimeoutException e)
        {
            Logger.Warn(e);
            client.Abort();
        }
        catch (Exception e)
        {
            Logger.Warn(e);
            client.Abort();
            throw;
        }
    }
}

This way, at the end of a request it will simply Abort the connection instead of closing it.

like image 193
Darek Avatar answered Nov 15 '22 06:11

Darek