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:
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);
});
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With