Реализация асинхронной модели, основанной на событияхImplementing the Event-based Asynchronous Pattern

Если вы создаете класс и некоторые его операции могут привести к значительным задержкам, подумайте о том, чтобы реализовать для этого класса асинхронные функции с помощью асинхронной модели на основе событий.If you are writing a class with some operations that may incur noticeable delays, consider giving it asynchronous functionality by implementing the Event-based Asynchronous Pattern.

Асинхронная модель на основе событий предоставляет стандартизированный способ упаковки класса, в котором есть асинхронные функции.The Event-based Asynchronous Pattern provides a standardized way to package a class that has asynchronous features. Если вы реализуете класс с помощью вспомогательных классов, таких как AsyncOperationManager, то ваш класс будет работать правильно в любой модели приложения, включая ASP.NET, консольные приложения и приложения Windows Forms.If implemented with helper classes like AsyncOperationManager, your class will work correctly under any application model, including ASP.NET, Console applications, and Windows Forms applications.

Пример, реализующий асинхронную модель на основе событий, см. в разделе Практическое руководство. Реализация компонента, поддерживающего асинхронную модель на основе событий.For an example that implements the Event-based Asynchronous Pattern, see How to: Implement a Component That Supports the Event-based Asynchronous Pattern.

Для простых асинхронных операций компонент BackgroundWorker может оказаться подходящим вариантом.For simple asynchronous operations, you may find the BackgroundWorker component suitable. Дополнительные сведения об использовании BackgroundWorker см. в разделе Практическое руководство. Фоновое выполнение операции.For more information about BackgroundWorker, see How to: Run an Operation in the Background.

Ниже перечислены функции асинхронной модели на основе событий, описанные в этом разделе.The following list describes the features of the Event-based Asynchronous Pattern discussed in this topic.

  • Возможности для реализации асинхронной модели на основе событийOpportunities for Implementing the Event-based Asynchronous Pattern

  • Именование асинхронных методовNaming Asynchronous Methods

  • Возможная поддержка отменыOptionally Support Cancellation

  • Возможная поддержка свойства IsBusyOptionally Support the IsBusy Property

  • Возможная поддержка отчетов о ходе выполненияOptionally Provide Support for Progress Reporting

  • Возможная поддержка возврата добавочных результатовOptionally Provide Support for Returning Incremental Results

  • Обработка выходных и ссылочных параметров в методахHandling Out and Ref Parameters in Methods

Возможности для реализации асинхронной модели на основе событийOpportunities for Implementing the Event-based Asynchronous Pattern

Подумайте о реализации асинхронной модели на основе событий при следующих условиях.Consider implementing the Event-based Asynchronous Pattern when:

  • Клиентам вашего класса не нужна доступность объектов WaitHandle и IAsyncResult для асинхронных операций. Это значит, что механизм опросов и WaitAll (или WaitAny) нужно создать на стороне клиента.Clients of your class do not need WaitHandle and IAsyncResult objects available for asynchronous operations, meaning that polling and WaitAll or WaitAny will need to be built up by the client.

  • Асинхронными операциями должен управлять клиент с использованием известной модели событий или делегатов.You want asynchronous operations to be managed by the client with the familiar event/delegate model.

Любую операцию можно реализовать асинхронно, но следует выбирать те операции, которые будут включать большие задержки.Any operation is a candidate for an asynchronous implementation, but those you expect to incur long latencies should be considered. Особенно подходят те операции, при которых клиенты вызывают метод и получают оповещение о его выполнении и никакого другого вмешательства в работу метода не требуется.Especially appropriate are operations in which clients call a method and are notified on completion, with no further intervention required. Также подходят операции, которые работают постоянно и периодически уведомляют клиентов о ходе выполнения, об изменении состояния или отправляют им промежуточные результаты.Also appropriate are operations which run continuously, periodically notifying clients of progress, incremental results, or state changes.

Дополнительные сведения о выборе асинхронной модели на основе событий см. в разделе Выбор асинхронной модели на основе событий.For more information on deciding when to support the Event-based Asynchronous Pattern, see Deciding When to Implement the Event-based Asynchronous Pattern.

Именование асинхронных методовNaming Asynchronous Methods

Для каждого синхронного метода имя_метода, для которого необходимо предоставить асинхронный эквивалент, выполните следующие действия.For each synchronous method MethodName for which you want to provide an asynchronous counterpart:

Определите метод MethodNameAsync, который отвечает за выполнение следующих действий:Define a MethodNameAsync method that:

  • Возвращает void.Returns void.

  • принимает те же параметры, что и метод имя_метода;Takes the same parameters as the MethodName method.

  • поддерживает несколько вызовов.Accepts multiple invocations.

Вы также можете определить перегрузку MethodNameAsync, которая идентична методу MethodNameAsync, но принимает дополнительный параметр userState, зависящий от объекта.Optionally define a MethodNameAsync overload, identical to MethodNameAsync, but with an additional object-valued parameter called userState. Реализуйте эту перегрузку, если необходимо управлять несколькими параллельными вызовами метода. В этом случае значение userState будет отправляться обратно во все обработчики событий, чтобы различать вызовы метода.Do this if you're prepared to manage multiple concurrent invocations of your method, in which case the userState value will be delivered back to all event handlers to distinguish invocations of the method. Такую перегрузку также можно использовать для хранения и последующего извлечения состояния пользователя.You may also choose to do this simply as a place to store user state for later retrieval.

Для каждой отдельной сигнатуры метода MethodNameAsync выполните следующие действия.For each separate MethodNameAsync method signature:

  1. Определите следующее событие в том же классе, в котором находится метод.Define the following event in the same class as the method:

    Public Event MethodNameCompleted As MethodNameCompletedEventHandler
    
    public event MethodNameCompletedEventHandler MethodNameCompleted;
    
  2. Определите следующий делегат и AsyncCompletedEventArgs.Define the following delegate and AsyncCompletedEventArgs. Обычно они определяются за пределами класса, но в том же пространстве имен.These will likely be defined outside of the class itself, but in the same namespace.

    Public Delegate Sub MethodNameCompletedEventHandler( _
        ByVal sender As Object, _
        ByVal e As MethodNameCompletedEventArgs)
    
    Public Class MethodNameCompletedEventArgs
        Inherits System.ComponentModel.AsyncCompletedEventArgs
    Public ReadOnly Property Result() As MyReturnType
    End Property
    
    public delegate void MethodNameCompletedEventHandler(object sender,
        MethodNameCompletedEventArgs e);
    
    public class MethodNameCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs
    {
        public MyReturnType Result { get; }
    }
    
    • Убедитесь, что класс MethodNameCompletedEventArgs предоставляет свои члены в виде свойств только для чтения, а не в виде полей, которые не поддерживают привязку данных.Ensure that the MethodNameCompletedEventArgs class exposes its members as read-only properties, and not fields, as fields prevent data binding.

    • Не определяйте производные от AsyncCompletedEventArgs классы для методов, которые не возвращают результатов.Do not define any AsyncCompletedEventArgs-derived classes for methods that do not produce results. Используйте в этой ситуации непосредственно экземпляр AsyncCompletedEventArgs.Simply use an instance of AsyncCompletedEventArgs itself.

      Примечание

      Также полностью допустимо использовать делегат и типы AsyncCompletedEventArgs повторно во всех случаях, когда это возможно и уместно.It is perfectly acceptable, when feasible and appropriate, to reuse delegate and AsyncCompletedEventArgs types. В этом случае имя делегата не будет согласовано с именем метода, так как делегат и AsyncCompletedEventArgs не привязаны к одному методу.In this case, the naming will not be as consistent with the method name, since a given delegate and AsyncCompletedEventArgs won't be tied to a single method.

Возможная поддержка отменыOptionally Support Cancellation

Если ваш класс будет поддерживать отмену асинхронных операций, возможность отмены должна быть предоставлена клиенту, как описано ниже.If your class will support canceling asynchronous operations, cancellation should be exposed to the client as described below. Перед тем как определить, будет ли реализована возможность отмены, необходимо ответить на два вопроса.Note that there are two decision points that need to be reached before defining your cancellation support:

  • Будет ли в вашем классе и во всех его будущих версиях всего одна асинхронная операция, которая поддерживает отмену?Does your class, including future anticipated additions to it, have only one asynchronous operation that supports cancellation?

  • Может ли асинхронная операция с возможностью отмены поддерживать несколько ожидающих операций?Can the asynchronous operations that support cancellation support multiple pending operations? То есть принимает ли метод MethodNameAsync параметр userState и позволяет ли он создать несколько вызовов перед ожиданием завершения любого из них?That is, does the MethodNameAsync method take a userState parameter, and does it allow multiple invocations before waiting for any to finish?

С помощью ответов на эти вопросы и приведенной ниже таблицы определите сигнатуру метода отмены.Use the answers to these two questions in the table below to determine what the signature for your cancellation method should be.

Visual BasicVisual Basic

Поддерживается несколько одновременных операцийMultiple Simultaneous Operations Supported Только одна операция в один момент времениOnly One Operation at a Time
Одна асинхронная операция во всем классеOne Async Operation in entire class Sub MethodNameAsyncCancel(ByVal userState As Object) Sub MethodNameAsyncCancel()
Несколько асинхронных операций в классеMultiple Async Operations in class Sub CancelAsync(ByVal userState As Object) Sub CancelAsync()

C#C#

Поддерживается несколько одновременных операцийMultiple Simultaneous Operations Supported Только одна операция в один момент времениOnly One Operation at a Time
Одна асинхронная операция во всем классеOne Async Operation in entire class void MethodNameAsyncCancel(object userState); void MethodNameAsyncCancel();
Несколько асинхронных операций в классеMultiple Async Operations in class void CancelAsync(object userState); void CancelAsync();

При определении метода CancelAsync(object userState) следует быть внимательным при выборе значений состояния клиента. Эти значения должны различаться не только для всех вызовов одного асинхронного метода, но и для всех вызываемых асинхронных методов объекта.If you define the CancelAsync(object userState) method, clients must be careful when choosing their state values to make them capable of distinguishing among all asynchronous methods invoked on the object, and not just between all invocations of a single asynchronous method.

Для версии метода с одной асинхронной операцией выбрано имя MethodNameAsyncCancel, чтобы его было удобнее искать в среде разработки, например в IntelliSense для Visual Studio.The decision to name the single-async-operation version MethodNameAsyncCancel is based on being able to more easily discover the method in a design environment like Visual Studio's IntelliSense. При этом связанные элементы группируются и отделяются от других элементов, которые не связаны с асинхронными операциями.This groups the related members and distinguishes them from other members that have nothing to do with asynchronous functionality. Если в дальнейшем могут быть добавлены новые асинхронные операции, рекомендуется определить CancelAsync.If you expect that there may be additional asynchronous operations added in subsequent versions, it is better to define CancelAsync.

Не определяйте несколько методов из приведенной выше таблицы в одном и том же классе.Do not define multiple methods from the table above in the same class. В этом не будет никакого смысла, к тому же интерфейс класса будет загроможден лишними методами.That will not make sense, or it will clutter the class interface with a proliferation of methods.

Эти методы обычно возвращают управление немедленно, и на самом деле операция может не быть отменена.These methods typically will return immediately, and the operation may or may not actually cancel. В обработчике события MethodNameCompleted объект MethodNameCompletedEventArgs содержит поле Cancelled, которое позволяет клиентам определить, была ли отменена операция.In the event handler for the MethodNameCompleted event, the MethodNameCompletedEventArgs object contains a Cancelled field, which clients can use to determine whether the cancellation occurred.

Придерживайтесь семантики отмены, которая описана в разделе Рекомендации по реализации асинхронной модели на основе событий.Abide by the cancellation semantics described in Best Practices for Implementing the Event-based Asynchronous Pattern.

Возможная поддержка свойства IsBusyOptionally Support the IsBusy Property

Если ваш класс поддерживает несколько одновременных вызовов, попробуйте предоставить свойство IsBusy.If your class does not support multiple concurrent invocations, consider exposing an IsBusy property. Это позволяет разработчикам определить, выполняется ли метод MethodNameAsync, не перехватывая исключение из метода MethodNameAsync.This allows developers to determine whether a MethodNameAsync method is running without catching an exception from the MethodNameAsync method.

Придерживайтесь семантики IsBusy, которая описана в разделе Рекомендации по реализации асинхронной модели на основе событий.Abide by the IsBusy semantics described in Best Practices for Implementing the Event-based Asynchronous Pattern.

Возможная поддержка отчетов о ходе выполненияOptionally Provide Support for Progress Reporting

Часто желательно, чтобы асинхронная операция сообщала о ходе своего выполнения.It is frequently desirable for an asynchronous operation to report progress during its operation. При использовании асинхронной модели на основе событий это можно реализовать.The Event-based Asynchronous Pattern provides a guideline for doing so.

  • При необходимости определите событие, которое будет выдаваться асинхронной операцией и вызываться в соответствующем потоке.Optionally define an event to be raised by the asynchronous operation and invoked on the appropriate thread. Объект ProgressChangedEventArgs содержит целочисленный индикатор хода выполнения, который может принимать значения от 0 до 100.The ProgressChangedEventArgs object carries an integer-valued progress indicator that is expected to be between 0 and 100.

  • Укажите следующее имя для этого события:Name this event as follows:

    • ProgressChanged, если в классе есть несколько асинхронных операций (или ожидается в будущих версиях);ProgressChanged if the class has multiple asynchronous operations (or is expected to grow to include multiple asynchronous operations in future versions);

    • MethodNameProgressChanged, если в классе есть одна асинхронная операция.MethodNameProgressChanged if the class has a single asynchronous operation.

    Выбор имени события соответствует выбору имени для метода отмены, как описано в разделе "Возможная поддержка отмены".This naming choice parallels that made for the cancellation method, as described in the Optionally Support Cancellation section.

В этом событии следует использовать сигнатуру делегата ProgressChangedEventHandler и класс ProgressChangedEventArgs.This event should use the ProgressChangedEventHandler delegate signature and the ProgressChangedEventArgs class. Кроме того, если есть возможность создать индикатор хода выполнения с более точной привязкой к домену (например, основанный на количестве полученных байт и общем количестве байт при операции скачивания), следует определить класс, производный от ProgressChangedEventArgs.Alternatively, if a more domain-specific progress indicator can be provided (for instance, bytes read and total bytes for a download operation), then you should define a derived class of ProgressChangedEventArgs.

Обратите внимание, что для этого класса существует только одно событие ProgressChanged или MethodNameProgressChanged, независимо от количества поддерживаемых асинхронных методов.Note that there is only one ProgressChanged or MethodNameProgressChanged event for the class, regardless of the number of asynchronous methods it supports. Чтобы различать изменения хода выполнения для нескольких одновременных операций, клиентам следует использовать объект userState, который передается в метод MethodNameAsync.Clients are expected to use the userState object that is passed to the MethodNameAsync methods to distinguish among progress updates on multiple concurrent operations.

Возможны ситуации, при которых несколько операций поддерживают ход выполнения и каждая операция возвращает собственный индикатор хода выполнения.There may be situations in which multiple operations support progress and each returns a different indicator for progress. В этом случае одним событием ProgressChanged не обойтись и необходимо рассмотреть поддержку нескольких событий ProgressChanged.In this case, a single ProgressChanged event is not appropriate, and you may consider supporting multiple ProgressChanged events. В этом случае используйте шаблон именования MethodNameProgressChanged для каждого метода MethodNameAsync.In this case use a naming pattern of MethodNameProgressChanged for each MethodNameAsync method.

Придерживайтесь семантики отчета о ходе выполнения, которая описана в разделе Рекомендации по реализации асинхронной модели на основе событий.Abide by the progress-reporting semantics described Best Practices for Implementing the Event-based Asynchronous Pattern.

Возможная поддержка возврата добавочных результатовOptionally Provide Support for Returning Incremental Results

Иногда асинхронная операция может возвращать добавочные результаты до своего завершения.Sometimes an asynchronous operation can return incremental results prior to completion. Поддержку этого сценария можно реализовать различными способами.There are a number of options that can be used to support this scenario. Ниже приведены некоторые примеры.Some examples follow.

Класс с одной операциейSingle-operation Class

Если ваш класс поддерживает только одну асинхронную операцию и эта операция может возвращать добавочные результаты, выполните следующие действия.If your class only supports a single asynchronous operation, and that operation is able to return incremental results, then:

  • Расширьте тип ProgressChangedEventArgs, включив в него результаты с приращением, и определите событие MethodNameProgressChanged с использованием этих дополнительных данных.Extend the ProgressChangedEventArgs type to carry the incremental result data, and define a MethodNameProgressChanged event with this extended data.

  • Создавайте это событие MethodNameProgressChanged при каждом появлении сведений о результатах.Raise this MethodNameProgressChanged event when there is an incremental result to report.

Это решение применимо только к классам с одной асинхронной операцией, чтобы не возникало конфликтов с информированием о добавочных результатах "по всем операциям" в том же событии, как в случае с событием имя_методаProgressChanged.This solution applies specifically to a single-async-operation class because there is no problem with the same event occurring to return incremental results on "all operations", as the MethodNameProgressChanged event does.

Класс с несколькими операциями, предоставляющий однородные добавочные результатыMultiple-operation Class with Homogeneous Incremental Results

В данном случае класс поддерживает несколько асинхронных методов, каждый из которых может возвращать добавочные результаты, и эти добавочные результаты имеют одинаковый тип данных.In this case, your class supports multiple asynchronous methods, each capable of returning incremental results, and these incremental results all have the same type of data.

Используйте модель, описанную выше для классов с одной операцией, так как эта же структура EventArgs подойдет для любых добавочных результатов.Follow the model described above for single-operation classes, as the same EventArgs structure will work for all incremental results. Определите событие ProgressChanged вместо события MethodNameProgressChanged, так как оно применяется к нескольким асинхронным методам.Define a ProgressChanged event instead of a MethodNameProgressChanged event, since it applies to multiple asynchronous methods.

Класс с несколькими операциями, предоставляющий неоднородные добавочные результатыMultiple-operation Class with Heterogeneous Incremental Results

Если ваш класс поддерживает несколько асинхронных методов, которые возвращают данные различных типов, необходимо сделать следующее.If your class supports multiple asynchronous methods, each returning a different type of data, you should:

  • Разделите отправляемые отчеты о добавочных результатах и отчеты о ходе выполнения.Separate your incremental result reporting from your progress reporting.

  • Определите отдельное событие MethodNameProgressChanged с соответствующими аргументами EventArgs для каждого асинхронного метода, чтобы обрабатывать добавочные сведения о результатах для этого метода.Define a separate MethodNameProgressChanged event with appropriate EventArgs for each asynchronous method to handle that method's incremental result data.

Вызовите этот обработчик события в соответствующем потоке, как описано в разделе Рекомендации по реализации асинхронной модели на основе событий.Invoke that event handler on the appropriate thread as described in Best Practices for Implementing the Event-based Asynchronous Pattern.

Обработка выходных и ссылочных параметров в методахHandling Out and Ref Parameters in Methods

Хотя в .NET в целом не рекомендуется использовать out или ref, это можно делать при соблюдении некоторых правил.Although the use of out and ref is, in general, discouraged in .NET, here are the rules to follow when they are present:

Для данного синхронного метода имя_метода:Given a synchronous method MethodName:

  • параметры out метода MethodName не должны быть частью метода MethodNameAsync.out parameters to MethodName should not be part of MethodNameAsync. Вместо этого они должны входить в метод MethodNameCompletedEventArgs с тем же именем, что и аналогичные параметры для MethodName (если нет более подходящего имени).Instead, they should be part of MethodNameCompletedEventArgs with the same name as its parameter equivalent in MethodName (unless there is a more appropriate name).

  • Параметры ref, передаваемые в метод MethodName должны входить в метод MethodNameAsync и MethodNameCompletedEventArgs с тем же именем, что и аналогичные параметры для MethodName (если нет более подходящего имени).ref parameters to MethodName should appear as part of MethodNameAsync, and as part of MethodNameCompletedEventArgs with the same name as its parameter equivalent in MethodName (unless there is a more appropriate name).

Например, если учитывать, что:For example, given:

Public Function MethodName(ByVal arg1 As String, ByRef arg2 As String, ByRef arg3 As String) As Integer
public int MethodName(string arg1, ref string arg2, out string arg3);

Асинхронный метод и его класс AsyncCompletedEventArgs будут выглядеть следующим образом:Your asynchronous method and its AsyncCompletedEventArgs class would look like this:

Public Sub MethodNameAsync(ByVal arg1 As String, ByVal arg2 As String)

Public Class MethodNameCompletedEventArgs
    Inherits System.ComponentModel.AsyncCompletedEventArgs
    Public ReadOnly Property Result() As Integer
    End Property
    Public ReadOnly Property Arg2() As String
    End Property
    Public ReadOnly Property Arg3() As String
    End Property
End Class
public void MethodNameAsync(string arg1, string arg2);

public class MethodNameCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs
{
    public int Result { get; };
    public string Arg2 { get; };
    public string Arg3 { get; };
}

См. такжеSee also