Я боролся с прокси-серверами 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()
?