Как правильно использовать прокси 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) метод должен быть вызван более одного раза. Метод может выбрать ничего не делать после первого вызова. ")

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

Решение: прокси-клиент ClientBase и перехват вызовов для 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() адекватна?

1 ответ

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

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

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

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

Примечание: обратите внимание, что в случае 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;
        }
    }
}
Другие вопросы по тегам