Рекомендации по реализации асинхронной модели, основанной на событиях

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

Для получения общих сведений см. Реализация асинхронной модели, основанной на событиях.

В следующем списке приведены рекомендации, описанные в этом разделе:

  • Требуемые гарантии поведения

  • Выполнение

  • Выполненное событие и EventArgs

  • Одновременно выполняемые операции

  • Доступ к результатам

  • Отчет о ходе выполнения

  • Реализация IsBusy

  • Отмена

  • Ошибки и исключения

  • Поточность и контексты

  • Рекомендации

Требуемые гарантии поведения

При реализации асинхронной модели, основанной на событии, необходимо предоставить ряд гарантий правильного поведения класса, чтобы клиенты этого класса могли полагаться на это поведение.

Выполнение

Всегда вызывайте обработчик события имя_методаCompleted при успешном завершении, ошибке или отмене. Приложения никогда не должны попадать в ситуацию, в которой они будут простаивать и завершение никогда не произойдет. Единственным исключением из этого правила является наличие такой асинхронной операции, которая разработана таким образом, что не может завершиться сама.

Выполненное событие и EventArgs

Для каждого отдельного метода имя_методаAsync используйте следующие требования к разработке:

  • Определите событие имя_методаCompleted для того же класса, что и метод.

  • Определите класс EventArgs и сопутствующий делегат для события имя_методаCompleted, который является производным из класса AsyncCompletedEventArgs. Имя класса по умолчанию должно быть в форме имя_методаCompletedEventArgs.

  • Убедитесь, что класс EventArgs является особым для возвращаемых значений метода имя_метода. При использовании класса EventArgs никогда не следует требовать от разработчиков приведения результатов.

    В следующем примере кода показана правильная и неправильная реализация этого требования к проектированию.

[C#]

// Good design
private void Form1_MethodNameCompleted(object sender, xxxCompletedEventArgs e) 
{ 
    DemoType result = e.Result;
}

// Bad design
private void Form1_MethodNameCompleted(object sender, MethodNameCompletedEventArgs e) 
{ 
    DemoType result = (DemoType)(e.Result);
}
  • Не определяйте какой-либо класс EventArgs для возвращения методов, которые возвращают void. Вместо этого используйте экземпляр класса AsyncCompletedEventArgs.

  • Убедитесь, что всегда создается событие имя_методаCompleted. Это событие должно создаваться при успешном завершении, при возникновении ошибки и при отмене. Приложения никогда не должны попадать в ситуацию, в которой они будут простаивать и завершение никогда не произойдет.

  • Убедитесь, что перехватываются любые исключения, которые возникают в асинхронной операции, и присвойте перехваченное исключение свойству Error.

  • Если при завершении задачи произошла ошибка, результаты должны быть недоступны. Если свойство Error не приняло значение null, убедитесь, что при осуществлении доступа к какому-либо свойству в структуре EventArgs создается исключение. Для выполнения такой проверки используйте метод RaiseExceptionIfNecessary.

  • Смоделируйте истечение времени ожидания в качестве ошибки. При истечении времени ожидания создайте событие имя_методаCompleted и присвойте TimeoutException свойству Error.

  • Если класс поддерживает несколько одновременных вызовов, убедитесь, что событие имя_методаCompleted содержит подходящий объект userSuppliedState.

  • Убедитесь, что событие имя_методаCompleted создается в соответствующем потоке и в необходимое время в жизненном цикле приложения. Дополнительные сведения см. в подразделе, посвященном поточности и контекстам.

Одновременно выполняемые операции

  • Если класс поддерживает несколько одновременных вызовов, разрешите разработчику отслеживать каждый вызов отдельно путем определения перегрузки имя_методаAsync, которая принимает параметр состояния объекта или идентификатор задачи userSuppliedState. Этот параметр всегда должен быть последним параметром в сигнатуре метода имя_методаAsync.

  • Если класс определяет перегрузку имя_методаAsync, которая принимает параметр состояния объекта или идентификатор задачи, отследите жизненный цикл операции с этим идентификатором задачи и предоставьте его обратно в обработчик завершения. Можно использовать вспомогательные классы. Дополнительные сведения об управлении параллельным выполнением см. в разделе Пошаговое руководство. Реализация компонента, поддерживающего асинхронную модель, основанную на событиях.

  • Если в классе определяется метод имя_методаAsync без параметра состояния и этот класс не поддерживает несколько одновременных вызовов, убедитесь, что любые попытки вызова имя_методаAsync до завершения предыдущего вызова имя_методаAsync приведут к созданию исключения InvalidOperationException.

  • В целом, не создавайте исключение, если метод имя_методаAsync без параметра userSuppliedState был вызван несколько раз, чтобы одновременно выполнялись несколько незавершенных операций. Можно создать исключение, если класс явно не может работать в этой ситуации, однако учитывайте, что разработчики могут обрабатывать эти неотличимые обратные вызовы.

Доступ к результатам

  • Если во время выполнения асинхронной операции произошла ошибка, доступ к результатам предоставлен не будет. Убедитесь, что доступ к любому свойству в AsyncCompletedEventArgs при значении свойства Error не равном null, приводит к созданию исключения, на которое ссылается свойство Error. Для этих целей класс AsyncCompletedEventArgs предоставляет метод RaiseExceptionIfNecessary.

  • Убедитесь, что любая попытка доступа к результатам приводит к созданию исключения InvalidOperationException, в котором говорится об отмене операции. Для выполнения такой проверки используйте метод AsyncCompletedEventArgs.RaiseExceptionIfNecessary.

Отчет о ходе выполнения

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

  • При реализации события ProgressChanged/имя_методаProgressChanged убедитесь, что отсутствуют события, созданные для определенной асинхронной операции после создания события имя_методаCompleted, относящегося к этой операции.

  • Если заполняется стандартный объект ProgressChangedEventArgs, убедитесь, что свойство ProgressPercentage всегда может восприниматься как проценты. Проценты не должны быть точными, но должны быть представлены как проценты. Если шкала хода выполнения выражается не в процентах, следует сделать производный класс из ProgressChangedEventArgs и оставить ProgressPercentage равным 0. Избегайте использования шкалы хода выполнения, отличной от процентной.

  • Убедитесь, что событие ProgressChanged создается в соответствующем потоке и в подходящее время жизненного цикла приложения. Дополнительные сведения см. в подразделе, посвященном поточности и контекстам.

Реализация IsBusy

  • Не предоставляйте свойство IsBusy, если класс поддерживает несколько одновременных вызовов. Например, прокси веб-служб XML не предоставляют свойство IsBusy, так как эти прокси поддерживают несколько одновременных вызовов асинхронных методов.

  • Свойство IsBusy должно возвращать значение true после вызова метода название_методаAsync и до создания события название_методаCompleted. В противном случае возвращается значение false. Компоненты BackgroundWorker и WebClient являются примерами классов, которые предоставляют свойство IsBusy.

Отмена

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

  • В случае отмены установите флаг Cancelled в объекте AsyncCompletedEventArgs.

  • Убедитесь, что любая попытка доступа к результатам приводит к созданию исключения InvalidOperationException, в котором говорится об отмене операции. Для выполнения такой проверки используйте метод AsyncCompletedEventArgs.RaiseExceptionIfNecessary.

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

  • Создайте событие имя_методаCompleted при отмене операции.

Ошибки и исключения

  • Перехватите любые исключения, которые возникают в асинхронной операции, и задайте значение свойства AsyncCompletedEventArgs.Error для этого исключения.

Поточность и контексты

Для правильной работы класса очень важно, чтобы обработчики событий клиента вызывались в правильном потоке или контексте для данной модели приложения, включая приложения ASP.NET и Windows Forms. Два важных вспомогательных класса предоставлены для того, чтобы гарантировать правильное поведение пользовательского асинхронного класса в любой модели приложения. Это классы AsyncOperation и AsyncOperationManager.

AsyncOperationManager предоставляет один метод CreateOperation, который возвращает AsyncOperation. Пользовательский метод имя_методаAsync вызывает метод CreateOperation, а класс использует возвращенный объект AsyncOperation для отслеживания жизненного цикла асинхронной задачи.

Чтобы сообщать клиенту сведения о ходе выполнения, нарастающих результатах и завершении, следует вызывать методы Post и OperationCompleted объекта AsyncOperation. AsyncOperation отвечает за вызовы маршалинга клиентских обработчиков событий в допустимый поток или контекст.

ПримечаниеПримечание

Можно обойти эти правила, если необходимо явно нарушить политику модели приложения, и при этом получить преимущества использования асинхронной модели, основанной на событиях.Например, может понадобиться, чтобы класс, выполняющийся в Windows Forms, был свободно-потоковым.Можно создать свободно-потоковый класс, если разработчики понимают применимые ограничения.Консольные приложения не синхронизируют выполнение вызовов Post.Это может нарушить порядок возникновения событий ProgressChanged.Если требуется сериализованное выполнение вызовов Post, следует реализовать и вызвать класс System.Threading.SynchronizationContext.

Дополнительные сведения об использовании AsyncOperation и AsyncOperationManager для разрешения асинхронных операций см. Пошаговое руководство. Реализация компонента, поддерживающего асинхронную модель, основанную на событиях.

Рекомендации

  • В идеале, каждый вызов метода должен не зависеть от других вызовов. Следует избегать связывания вызовов с общими ресурсами. Если ресурсы должны совместно использоваться разными вызовами, следует предоставить в реализации допустимый механизм синхронизации.

  • Настоятельно рекомендуются разработки, в которых клиент должен реализовывать синхронизацию. Например, можно иметь асинхронный метод, который получает глобальный статический объект в качестве параметра; несколько одновременных вызовов такого метода может привести к повреждению данных или к взаимоблокировкам.

  • Если реализовывать метод с перегрузкой нескольких вызовов (userState в сигнатуре), класс должен управлять коллекцией пользовательских состояний или идентификаторов задач, а также соответствующими операциями в очереди. Эта коллекция должна быть защищена с помощью областей lock, так как различные вызовы приводят к добавлению и удалению объектов userState в коллекции.

  • Рассмотрите возможность повторного использования классов CompletedEventArgs, если это приемлемо и допустимо. В этом случае именование не будет согласованным с именем метода, потому что данный делегат и тип EventArgs не привязаны к одному методу. Однако никогда не следует заставлять разработчиков приводить значение, полученное из этого свойства, к EventArgs.

  • При создании класса, который является производным из Component, не реализовывайте и не устанавливайте собственный класс SynchronizationContext. Используемым объектом SynchronizationContext управляют модели приложения, а не компоненты.

  • При использовании многопоточных программ возникает опасность весьма серьезных и сложных ошибок. Ознакомьтесь с разделом Рекомендации по работе с потоками перед реализацией любых решений, использующих многопоточность.

См. также

Задачи

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

Пошаговое руководство. Реализация компонента, поддерживающего асинхронную модель, основанную на событиях

Ссылки

AsyncOperation

AsyncOperationManager

AsyncCompletedEventArgs

ProgressChangedEventArgs

BackgroundWorker

Основные понятия

Реализация асинхронной модели, основанной на событиях

Определение, когда следует реализовать асинхронную модель, основанную на событиях

Рекомендации по реализации асинхронной модели, основанной на событиях

Другие ресурсы

Многопоточное программирование с использованием асинхронной модели, основанной на событиях