What happens when I close/abort a WCF channel/proxy?

What happens when I close/abort a WCF channel/proxy?

 

I'm trying to get a better understanding of what's going on when I use a WCF proxy. I'm having trouble understanding what happens when I close (or don't close) a proxy.

  • What's going on when I call Close() or Abort() on a WCF proxy? What's the difference?
  • How does it differ between types of bindings (like, a sessionless BasicHttpBinding vs. something sessionful)?
  • Why can Close() throw in certain situations and why can it be a blocking operation?

 

 

Closing WCF client
A client has an inherited responsibility of gracefully closing the connection. It is always recommended to close a proxy client. If the binding between a client and a service is transport-layer sessionful, then closing a proxy is essential to tear down the connection between both parties. Service has a payload threshold defined for concurrent connections. If the number of concurrent connections goes above this threshold linearly then the overall service performance decreases exponentially. This is why it is crucial to dispose of the connection as soon as possible. Closing the proxy also notifies the service instance that it is no longer in use and may be collected by GC (subject to service instance management). If the client does not close a connection, it is still automatically torn down by WCF timeouts (found in the configuration files).

Aborting WCF client
In the situation where there is a fault in the service-client interaction, the objects on both ends are potentially totally broken. Thus using a proxy after the exception is not advised. Given the WCF binding use transport sessions, the client after a fault would not even be able to close it (if there was no transport layer session then the client could use or close the proxy, but this is not recommended as the configuration of sessions could change). So after a fault has happened the only safe operation is to abort a proxy.

Close is a synchronous operation, it can throw if the transport session has been damaged by a fault and it is a blocking operation until a confirmatory response from service is received (true for some bindings).

  • 2
    If the number of concurrent connections goes above this threshold linearly then the overall service performance decreases exponentially. Can you please help to understand this point.   Mar 8 '13 at 12:27
  • 1
    It's just something I observed in practice - load and resources usually behave in this way. If one increases the load, throughput performance increase. However, at some point (saturation point) the load gets as high as a box can normally handle. At this point you get max performance. If you keep increasing the load, the service won't die immediately but it will "buffer" the requests. This helps to solve pick load traffic as usually it lasts tiny amount of time. But in our example we will continue increasing the load and fill some internal buffers. After saturation perf degrades much faster. 
    – oleksii
     Nov 9 '15 at 17:16
1
I still am not understanding what Abort does or, more importantly, what can go wrong if Abort is not called. Does it only matter if you're trying to re-use your proxy client instance, would not calling Abort affect new instances, and/or is it a memory leak issue? 
– xr280xr
 Feb 25 '20 at 5:00 
 
 

https://gist.github.com/RobinHerbots/1308811

        private class ChannelStruct : IDisposable
        {
            private readonly TChannel _channel;
            private readonly IClientChannel _clientChannel;
            private readonly long _originalOperationTimeoutTicks;
            private readonly DateTime _creationTime;

            public ChannelStruct(ChannelFactory<TChannel> channelFactory)
            {
                _channel = channelFactory.CreateChannel();
                _clientChannel = _channel as IClientChannel;
                _creationTime = DateTime.Now;
                _originalOperationTimeoutTicks = ((IClientChannel)_channel).OperationTimeout.Ticks;
            }

            public CommunicationState State
            {
                get
                {
                    return _clientChannel.State;
                }
            }

            public TChannel Channel
            {
                get
                {
                    if (State == CommunicationState.Created)
                        _clientChannel.Open();
                    return _channel;
                }
            }

            public bool TimeShift()
            {
                TimeSpan currentTimeSpan = DateTime.Now - _creationTime;
                var operationTimeLeft = _clientChannel.OperationTimeout.Ticks - currentTimeSpan.Ticks;

                if (operationTimeLeft <= 100) //System.Timeout - proxy is idle or almost
                {
                    Debug.WriteLine("Dispose Proxy " + typeof(TChannel) + " is idle " + operationTimeLeft);
                    Dispose(); //prevent exception
                    return false;
                }
                //add extra time
                var extraTime = _originalOperationTimeoutTicks - (operationTimeLeft);
                _clientChannel.OperationTimeout = new TimeSpan(_clientChannel.OperationTimeout.Ticks + extraTime);
                Debug.WriteLine("Timeshift Proxy " + typeof(TChannel) + " - " + extraTime);

                return true;
            }

            #region IDisposable Members
            public void Dispose()
            {
                switch (_clientChannel.State)
                {
                    case CommunicationState.Closed:
                    case CommunicationState.Closing:
                        break;
                    case CommunicationState.Opened:
                        try
                        {
                            _clientChannel.Close(); //gracefully close
                        }
                        catch (Exception)
                        {
                            _clientChannel.Abort(); //on exception kill the connection
                        }
                        break;
                    default:
                        _clientChannel.Abort();
                        break;
                }
            }

            #endregion
        }

  

The Proper Use and Disposal of WCF Channels (or CommunicationObjectFaultedException ?!*#*$)

 

In this post, I explore the subtle but disastrous consequences of expecting a 'using' block to clean up your WCF channels.

In this post, I explore the subtle but disastrous consequences of expecting a using block to clean up your WCF channels.

NOTE: The examples show the use of a generated proxy but the issue and solution applies to all ICommunicationObject including generated proxies (ClientBase<T>) as well as ChannelFactory and ChannelFactory<T>.

Many sleepless nights have been spent wondering why, regardless of any exception handling, the following code throws a CommunicationObjectFaultedException when .HelloWorld() throws, even though the exception is being swallowed.

Typical 'using' Disposal Pattern

C#
using (WCFServiceClient c = new WCFServiceClient())
{
    try
    {
        c.HelloWorld();
    }
    catch (Exception ex)
    {
        // handle the exception
    }
} 

Consider that when an exception occurs during the use of a channel, it enters the Faulted state and, in this Faulted state, calling .Close() will throw a CommunicationObjectFaultedException.

The using statement, as you know, ensures that .Dispose() is called before the using block is closed. For channels, which typically have private .Dispose() methods, .Dispose() simply calls .Close(). Aha! Are you picking up what I am putting down?

The trap of the typical using disposal pattern illustrated:

C#
using (WCFServiceClient c = new WCFServiceClient())
{
    try
    {
        c.HelloWorld();
    }
    catch (Exception ex)
    {
        // You don't know it yet but your mellow has just been harshed.

        // If you handle this exception and fall through you will 
        // still be cheerfully greeted with an unhandled 
        // CommunicationObjectFaultedException when 'using' tries to .Close() the client.

        // If you throw or re-throw from here you will never see that exception, 
        // it is gone forever. 
        // buh bye.
        // All you will get is an unhandled CommunicationObjectFaultedException
    }
} // <-- here is where the CommunicationObjectFaultedException is thrown

The solution to this problem is to ensure that the channel can successfully transition to the Closed state upon closure of the using block. This is done by acknowledging the Faulted state by calling .Abort() from within your catch, which actually does close the channel albeit abruptly. Any subsequent .Close() is a NOOP.

A proper using disposal pattern

C#
using (WCFServiceClient client = new WCFServiceClient())
{
    try
    {
        client.ThrowException();
        
    }
    catch (Exception ex)
    {
        // acknowledge the Faulted state and transition to Closed
        client.Abort();

        // handle the exception or rethrow, makes no nevermind to me, my
        // yob is done ;-D
    }
}

There are some scenarios where the shape of your surrounding code does not lend itself to using a using block.

While the using block does have its benefits, such as the scoping provided by the block, in the context of a channel all it does is call .Close() and we can do that easily enough.

Proper use of a channel without using

C#
WCFServiceClient c = new WCFServiceClient();

try
{
    c.HelloWorld();
}
catch
{
    // acknowledge the Faulted state and transition to Closed
    c.Abort();

    // handle or throw
    throw;
}
finally
{
    c.Close();
}

There you have it, my take on the proper use and disposal of WCF channels.

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

 

 
posted @ 2022-01-17 15:59  PanPan003  阅读(128)  评论(0编辑  收藏  举报