Как правильно удалить прокси-сервер WCF?

Я боролся с прокси-серверами WCF. Как правильно удалить прокси-сервер WCF? Ответ нетривиальный.

System.ServiceModel.ClientBase нарушает собственный шаблон Dispose от Microsoft

System.ServiceModel.ClientBase<TChannel> действительно реализует IDisposable, поэтому следует предполагать, что он должен быть удален или использован в using-блоке. Это лучшие практики для всего одноразового использования. Однако реализация является явной, поэтому нужно явно приводить экземпляры ClientBase к IDisposable, что затрудняет возникновение проблемы.

Однако самый большой источник путаницы заключается в том, что вызов Dispose() на ClientBase экземплярах, в которых произошел сбой, даже каналах с ошибкой, потому что они никогда не открывались в первую очередь, приведет к возникновению исключения. Это неизбежно означает, что значимое исключение, объясняющее ошибку, немедленно теряется, когда стек раскручивается, область using заканчивается и Dispose() генерирует бессмысленное исключение, говорящее о том, что вы не можете удалить неисправный канал.

Вышеупомянутое поведение является анафемой для шаблона dispose, который гласит, что объекты должны быть терпимы к множественным явным вызовам Dispose(). (см. http://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx, "... разрешить вызов метода Dispose(bool) более одного раза. Метод может ничего не делать после первого вызова. ")

С появлением инверсии управления эта плохая реализация становится реальной проблемой. I.O.C. контейнеры (в частности, Ninject) обнаруживают IDisposable интерфейс и явно вызывают Dispose() для активированных экземпляров в конце области внедрения.

Решение: вызовы Proxy ClientBase и Intercept для Dispose ()

Мое решение заключалось в том, чтобы прокси ClientBase путем создания подкласса System.Runtime.Remoting.Proxies.RealProxy и перехватывать или перехватывать вызовы Dispose(). Моя первая замена на Dispose() выглядела примерно так:

if (_client.State == CommunicationState.Faulted) _client.Abort();
else ((IDisposable)_client).Dispose();

(Обратите внимание, что _client - это типизированная ссылка на цель прокси.)

Проблемы с NetTcpBinding

Сначала я подумал, что это решило проблему, но затем я обнаружил проблему в производстве: при определенных сценариях, которые было чертовски трудно воспроизвести, я обнаружил, что каналы, использующие NetTcpBinding, не закрывались должным образом в случае неисправности, хотя Dispose призвал _client.

У меня было приложение ASP.NET MVC, использующее мою реализацию прокси для подключения к службе WCF с использованием NetTcpBinding в локальной сети, размещенной в службе Windows NT в кластере служб только с одним узлом. Когда я провел нагрузочное тестирование приложения MVC, определенные конечные точки в службе WCF (которая использовала общий доступ к портам) через некоторое время перестали отвечать.

Я изо всех сил пытался воспроизвести это: одни и те же компоненты, работающие по локальной сети между двумя машинами разработчика, работали идеально; консольное приложение, забивающее реальные конечные точки WCF (работающие в промежуточном сервисном кластере) с множеством процессов и множеством потоков в каждом работающем; настройка приложения MVC на промежуточном сервере для подключения к конечным точкам на машине разработчика, работающей под нагрузкой; запуск приложения MVC на компьютере разработчика и подключение к промежуточным конечным точкам WCF сработали. Однако последний сценарий работал только в IIS Express, и это был прорыв. Конечные точки будут перегружены при нагрузочном тестировании приложения MVC в полнофункциональном IIS на компьютере разработчика, подключенном к промежуточному кластеру служб.

Решение: закройте канал

Не сумев понять проблему и прочитав много-много страниц MSDN и других источников, которые утверждали, что проблема не должна существовать вообще, я попробовал сделать дальний план и изменил свой Dispose() обходной путь на ...

if (_client.State == CommunicationState.Faulted) _client.Abort();
else if (_client.State == CommunicationState.Opened)
{
    ((IContextChannel)_client.Channel).Close();
    ((IDisposable)_client).Dispose();
}
else ((IDisposable)_client).Dispose();

... и проблема перестала возникать во всех тестовых настройках и под нагрузкой в ​​тестовой среде!

Почему?

Может ли кто-нибудь объяснить, что могло происходить и почему явное закрытие Channel перед вызовом Dispose() решило эту проблему? Насколько я могу судить, в этом не должно быть необходимости.

Наконец, я возвращаюсь к открывающему вопросу: как правильно удалить прокси-сервер WCF? Подходит ли моя замена для Dispose()?


person Xharlie    schedule 12.12.2014    source источник


Ответы (1)


Проблема, насколько мне удалось понять, заключается в том, что вызов Dispose отключает дескриптор, но на самом деле не закрывает канал, который затем удерживает ресурсы, а затем в конечном итоге истекает время ожидания.

Вот почему ваша служба перестала отвечать через некоторое время во время нагрузочного тестирования: потому что первоначальные вызовы удерживали ресурсы дольше, чем вы думали, а последующие вызовы не могли использовать эти ресурсы.

Я пришел к следующему решению. Предпосылка решения состоит в том, что вызова Dispose должно быть достаточно, чтобы избавиться от дескриптора, а также закрыть канал. Дополнительным преимуществом является то, что, если клиент попадает в неисправное состояние, он воссоздается, чтобы последующие вызовы были успешными.

Если ServiceClient<TService> вводится в другой класс через структуру внедрения зависимостей, такую ​​как Ninject, тогда все ресурсы будут освобождены должным образом.

NB: обратите внимание, что в случае Ninject привязка должна определять область, то есть она не должна пропускать InXyzScope или определяться с помощью InTransientScope. Если область действия не имеет смысла, используйте InCallScope.

Вот что я придумал:

public class ServiceClient<TService> : IDisposable
{
    private readonly ChannelFactory<TService> channelFactory;

    private readonly Func<TService> createChannel;

    private Lazy<TService> service;

    public ServiceClient(ChannelFactory<TService> channelFactory)
        : base()
    {
        this.channelFactory = channelFactory;

        this.createChannel = () =>
        {
            var channel = ChannelFactory.CreateChannel();

            return channel;
        };

        this.service = new Lazy<TService>(() => CreateChannel());
    }

    protected ChannelFactory<TService> ChannelFactory
    {
        get
        {
            return this.channelFactory;
        }
    }

    protected Func<TService, bool> IsChannelFaulted
    {
        get
        {
            return (service) =>
                {
                    var channel = service as ICommunicationObject;

                    if (channel == null)
                    {
                        return false;
                    }

                    return channel.State == CommunicationState.Faulted;
                };
        }
    }

    protected Func<TService> CreateChannel
    {
        get
        {
            return this.createChannel;
        }
    }

    protected Action<TService> DisposeChannel
    {
        get
        {
            return (service) =>
                {
                    var channel = service as ICommunicationObject;

                    if (channel != null)
                    {
                        switch (channel.State)
                        {
                            case CommunicationState.Faulted:
                                channel.Abort();
                                break;

                            case CommunicationState.Closed:
                                break;

                            default:
                                try
                                {
                                    channel.Close();
                                }
                                catch (CommunicationException)
                                {
                                }
                                catch (TimeoutException)
                                {
                                }
                                finally
                                {
                                    if (channel.State != CommunicationState.Closed)
                                    {
                                        channel.Abort();
                                    }
                                }
                                break;
                        }
                    }
                };
        }
    }

    protected Action<ChannelFactory<TService>> DisposeChannelFactory
    {
        get
        {
            return (channelFactory) =>
                {
                    var disposable = channelFactory as IDisposable;

                    if (disposable != null)
                    {
                        disposable.Dispose();
                    }
                };
        }
    }

    public void Dispose()
    {
        DisposeChannel(this.service.Value);
        DisposeChannelFactory(this.channelFactory);
    }

    public TService Service
    {
        get
        {
            if (this.service.IsValueCreated && IsChannelFaulted(this.service.Value))
            {
                DisposeChannel(this.service.Value);

                this.service = new Lazy<TService>(() => CreateChannel());
            }

            return this.service.Value;
        }
    }
}
person Umar Farooq Khawaja    schedule 14.08.2015