Background
The Issues
The issues faced by .NET developers are explained in depth in the article How To Call WCF Services Properly, where I researched and highlighted the typical problems with using WCF clients (such as memory and connection leaks), and concluded that the most correct solution for consuming WCF services would:
- Perform the Close/Abort pattern without a race condition;
- Handle the situation when the service operation throws exceptions;
- Handle the situations when both the
Close
and Abort
methods throw exceptions; and
- Handle asynchronous exceptions such as the
ThreadAbortException
.
Below is the verbose sample code that is required for correctly using a .NET WCF client, performing exception handling and correctly performing the Close/Abort pattern.
Unfortunately, due to the way Microsoft designed WCF, if you want to use a WCF service client cleanly without connection or memory leaks, this is the type of extensive code that must be written for every service operation call.
1SampleServiceClient client = null;
2
3try
4{
5 client = new SampleServiceClient();
6
7 var response = client.SampleOperation(1234);
8
9 // Do some business logic
10}
11catch (FaultException<MyCustomException>)
12{
13 // Do some business logic for this SOAP Fault Exception
14}
15catch (FaultException)
16{
17 // Do some business logic for this SOAP Fault Exception
18}
19catch (CommunicationException)
20{
21 // Catch this expected exception so it is not propagated further.
22 // Perhaps write this exception out to log file for gathering statistics...
23}
24catch (TimeoutException)
25{
26 // Catch this expected exception so it is not propagated further.
27 // Perhaps write this exception out to log file for gathering statistics...
28}
29catch (Exception)
30{
31 // An unexpected exception that we don't know how to handle.
32 // Perhaps write this exception out to log file for support purposes...
33 throw;
34}
35finally
36{
37 // This will:
38 // - be executed if any exception was thrown above in the 'try' (including ThreadAbortException); and
39 // - ensure that CloseOrAbortServiceChannel() itself will not be interrupted by a ThreadAbortException
40 // (since it is executing from within a 'finally' block)
41 CloseOrAbortServiceChannel(client);
42
43 // Unreference the client
44 client = null;
45}
46
47
48private void CloseOrAbortServiceChannel(ICommunicationObject communicationObject)
49{
50 bool isClosed = false;
51
52 if (communicationObject == null || communicationObject.State == CommunicationState.Closed)
53 {
54 return;
55 }
56
57 try
58 {
59 if (communicationObject.State != CommunicationState.Faulted)
60 {
61 communicationObject.Close();
62 isClosed = true;
63 }
64 }
65 catch (CommunicationException)
66 {
67 // Catch this expected exception so it is not propagated further.
68 // Perhaps write this exception out to log file for gathering statistics...
69 }
70 catch (TimeoutException)
71 {
72 // Catch this expected exception so it is not propagated further.
73 // Perhaps write this exception out to log file for gathering statistics...
74 }
75 catch (Exception)
76 {
77 // An unexpected exception that we don't know how to handle.
78 // Perhaps write this exception out to log file for support purposes...
79 throw;
80 }
81 finally
82 {
83 // If State was Faulted or any exception occurred while doing the Close(), then do an Abort()
84 if (!isClosed)
85 {
86 AbortServiceChannel(communicationObject);
87 }
88 }
89}
90
91
92private static void AbortServiceChannel(ICommunicationObject communicationObject)
93{
94 try
95 {
96 communicationObject.Abort();
97 }
98 catch (Exception)
99 {
100 // An unexpected exception that we don't know how to handle.
101 // If we are in this situation:
102 // - we should NOT retry the Abort() because it has already failed and there is nothing to suggest it could be successful next time
103 // - the abort may have partially succeeded
104 // - the actual service call may have been successful
105 //
106 // The only thing we can do is hope that the channel's resources have been released.
107 // Do not rethrow this exception because the actual service operation call might have succeeded
108 // and an exception closing the channel should not stop the client doing whatever it does next.
109 //
110 // Perhaps write this exception out to log file for gathering statistics and support purposes...
111 }
112}
ChannelAdam WCF Library Makes It Easy
The ChannelAdam WCF Library is an API for working with WCF clients, that significantly reduces the amount of that code that developers have to write.
It allows you to forget about the complex subtleties of WCF clients, channels and communication states, and lets you just use your service client in a way that is very similar to how you probably are doing it now - but without the leaks!
When you use the ChannelAdam WCF Library’s ServiceConsumer
, you know that you can keep calling service operations through the ServiceConsumer
and it will ‘just work’ and not leak connections or memory - no matter what happens!
The ChannelAdam WCF Library’s ServiceConsumer
hides the inner complexity of service channels.
For example, if a service channel / communication object / service client moves into a Fault state, the ServiceConsumer
will correctly perform the Close/Abort pattern and dispose of the channel.
The next time a request is made through the ServiceConsumer
, a new channel is created under the covers to seamlessly process the request.