I have been struggling with WCF Proxies. What is the correct way to Dispose a WCF Proxy? The answer is not trivial.
System.ServiceModel.ClientBase violates Microsoft's own Dispose-pattern
System.ServiceModel.ClientBase<TChannel>
does implement IDisposable
so one must assume that it should be disposed or used in a using
-block. These are best practices for anything disposable. The implementation is explicit, however, so one does have to explicitly cast ClientBase
instances to IDisposable
, clouding the issue.
The biggest source of confusion, however, is that calling Dispose()
on ClientBase
instances that faulted, even channels that faulted because they never opened in the first place, will result in an exception being thrown. This, inevitably, means that the meaningful exception explaining the fault is immediately lost when the stack unwinds, the using
scope ends and Dispose()
throws a meaningless exception saying that you can't dispose a faulted channel.
The above behaviour is anathema to the dispose pattern which states that objects must be tolerant of multiple explicit calls to Dispose()
. (see http://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx, "...allow the Dispose(bool)
method to be called more than once. The method might choose to do nothing after the first call.")
With the advent of inversion-of-control, this poor implementation becomes a real problem. I.O.C. containers (specifically, Ninject) detect the IDisposable
interface and call Dispose()
explicitly on activated instances at the end of an injection scope.
Solution: Proxy ClientBase and Intercept calls to Dispose()
My solution was to proxy ClientBase
by subclassing System.Runtime.Remoting.Proxies.RealProxy
and to hijack or intercept calls to Dispose()
. My first replacement for Dispose()
went something like this:
if (_client.State == CommunicationState.Faulted) _client.Abort();
else ((IDisposable)_client).Dispose();
(Note that _client
is a typed reference to the target of the proxy.)
Problems with NetTcpBinding
I thought that this had nailed it, initially, but then I discovered a problem in production: under certain scenarios that were fiendishly difficult to reproduce, I found that channels using a NetTcpBinding
were not closing properly in the unfaulted case, even though Dispose
was being called on _client
.
I had an ASP.NET MVC Application using my proxy implementation to connect to a WCF Service using a NetTcpBinding
on the local network, hosted within a Windows NT Service on a service cluster with only one node. When I load-tested the MVC Application, certain endpoints on the WCF Service (which was using port-sharing) would stop responding after a while.
I struggled to reproduce this: the same components running across the LAN between two developer's machines worked perfectly; a console application hammering the real WCF endpoints (running on the staging service cluster) with many processes and many threads in each worked; configuring the MVC Application on the staging server to connect to the endpoints on a developer's machine worked under load; running the MVC Application on a developer's machine and connecting to the staging WCF endpoints worked. The last scenario only worked under IIS Express, however, and this was a breakthrough. The endpoints would sieze up when load-testing the MVC Application under full-fat IIS on a developer's machine, connecting to the staging service cluster.
Solution: Close the Channel
After failing to understand the problem and reading many, many pages of the MSDN and other sources that claimed the problem shouldn't exist at all, I tried a long-shot and changed my Dispose()
work-around to...
if (_client.State == CommunicationState.Faulted) _client.Abort();
else if (_client.State == CommunicationState.Opened)
{
((IContextChannel)_client.Channel).Close();
((IDisposable)_client).Dispose();
}
else ((IDisposable)_client).Dispose();
... and the problem stopped occurring in all test setups and under load in the staging environment!
Why?
Can anyone explain what might have been happening and why explicitly closing the Channel
before calling Dispose()
solved it? As far as I can tell, this shouldn't be necessary.
Finally, I return to the opening question: What is the correct way to Dispose a WCF Proxy? Is my replacement for Dispose()
adequate?