Безопасное закрытие и прекращение выпуска ресурсов при удалении сетевых подключений

Пример UsingUsing демонстрирует использование методов Close и Abort для очистки ресурсов при работе с типизированным клиентом. Оператор using вызывает исключения, если сетевое подключение не является надежным. Этот пример основан на руководстве 'Начало работы', который реализует службу калькулятора. В этом примере клиентом является консольное приложение (выполнимый файл .exe), а служба размещается в службах Интернет-информации (IIS).

Примечание.

Процедура настройки и инструкции по построению для данного образца приведены в конце этого раздела.

В этом образце показаны две общие проблемы, которые могут возникнуть при использовании оператора "using" в C# с типизированными клиентами, а также код, который правильно выполняет очистку после исключений.

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

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

using (CalculatorClient client = new CalculatorClient())
{
    ...
} // <-- this line might throw
Console.WriteLine("Hope this code wasn't important, because it might not happen.");

Даже если ничего в блоке using не генерирует исключения или все исключения в блоке using перехвачены, вызов Console.WriteLine может не произойти, поскольку неявный вызов метода Dispose() при закрывающей фигурной скобке может вызвать исключение.

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

using (CalculatorClient client = new CalculatorClient())
{
    ...
    throw new ApplicationException("Hope this exception was not important, because "+
                                   "it might be masked by the Close exception.");
} // <-- this line might throw an exception.

Поскольку метод Dispose() происходит в блоке "finally", исключение ApplicationException никогда не видно за пределами блока "using", если метод Dispose() выполняется с ошибкой. Если внешний код должен знать, когда происходит исключение ApplicationException, конструкция "using" может стать причиной возникновения проблем, маскируя это исключение.

Наконец, в данном примере показано, как правильно выполнить очистку, если в DemonstrateCleanupWithExceptions происходят исключения. При этом используется блок try/catch для отчетности об ошибках и вызова Abort. Дополнительные сведения о перехвате исключений из вызовов клиентов см. в примере «Ожидаемые исключения».

try
{
    ...
    client.Close();
}
catch (CommunicationException e)
{
    ...
    client.Abort();
}
catch (TimeoutException e)
{
    ...
    client.Abort();
}
catch (Exception e)
{
    ...
    client.Abort();
    throw;
}

Примечание.

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

При выполнении примера ответы и исключения операций отображаются в окне консоли клиента.

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

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

=
= Demonstrating problem:  closing brace of using statement can throw.
=
Got System.ServiceModel.CommunicationException from Divide.
Got System.ServiceModel.Security.MessageSecurityException
=
= Demonstrating problem:  closing brace of using statement can mask other Exceptions.
=
Got System.ServiceModel.CommunicationException from Divide.
Got System.ServiceModel.Security.MessageSecurityException
=
= Demonstrating cleanup with Exceptions.
=
Calling client.Add(0.0, 0.0);
        client.Add(0.0, 0.0); returned 0
Calling client.Divide(0.0, 0.0);
Got System.ServiceModel.CommunicationException from Divide.

Press <ENTER> to terminate client.

Установка, сборка и выполнение примера

  1. Убедитесь, что вы выполнили процедуру однократной настройки для образцов Windows Communication Foundation.

  2. Чтобы создать выпуск решения на языке C# или Visual Basic .NET, следуйте инструкциям в разделе Building the Windows Communication Foundation Samples.

  3. Чтобы запустить пример в конфигурации с одним или несколькими компьютерами, следуйте инструкциям в разделе "Примеры Windows Communication Foundation".