2014-12-12 9 views
5

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

System.ServiceModel.ClientBase нарушает Microsoft собственного Dispose-модель

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

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

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

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

Решение: Proxy 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() адекватной?

ответ

0

Проблема, насколько я был в состоянии понять, заключается в том, что вызов 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; 
     } 
    } 
} 

 Смежные вопросы

  • Нет связанных вопросов^_^