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

Приложения, которые выполняют несколько задач одновременно и при этом способны поддерживать взаимодействие с пользователем, часто нуждаются в разработках, в которых можно использовать несколько потоков. Пространство имен System.Threading предоставляет все средства, необходимые для создания высокопроизводительных приложений, поддерживающих несколько потоков, однако эффективное использование этих средств требует большого опыта создания многопотоковых программных решений. Для относительно простых многопоточных приложений компонент BackgroundWorker предоставляет прямолинейное решение. Для более сложных асинхронных приложений рассмотрите возможность реализации класса, который следует асинхронной модели, основанной на событиях.

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

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

  • Запускать одновременно несколько операций, получая уведомление о завершении каждой из них.

  • Ожидать доступность ресурсов без остановки ("зависания") приложения.

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

Класс, поддерживающий асинхронную модель, основанную на событиях, будет содержать один или несколько методов имя_методаAsync. Эти методы могут отражать синхронные версии, которые выполняют то же действие с текущим потоком. Этот класс также может содержать событие имя_методаCompleted, а также метод имя_методаAsyncCancel (или просто CancelAsync).

PictureBox — это обычный компонент, который поддерживает асинхронную модель, основанную на событиях. Можно загрузить изображения синхронно посредством вызова метода Load, однако если изображение большое или сетевое подключение слишком медленное, приложение будет остановлено ("зависнет") до завершения операции загрузки и возврата вызова перегрузки Load.

Если необходимо, чтобы приложение продолжало работать при загрузке изображения, можно вызвать метод LoadAsync и обработать событие LoadCompleted, как обрабатывается любое другое событие. При вызове метода LoadAsync приложение продолжит работу, при этом загрузка будет выполняться в отдельном потоке ("в фоновом режиме"). Обработчик события будет вызван после завершения операции по загрузке изображения. Этот обработчик события может проверить параметр AsyncCompletedEventArgs для определения успешного завершения загрузки.

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

Предупреждающее замечаниеВнимание

Возможно, что загрузка будет завершена при отправке запроса методом CancelAsync, поэтому свойство Cancelled может не отражать запрос на отмену.Такая ситуация называется состоянием гонки и является часто возникающим вопросом в многопоточном программировании.Дополнительные сведения о вопросах, связанных с многопоточным программированием см. в разделе Рекомендации по работе с потоками.

Характеристики асинхронной модели, основанной на событиях

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

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

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

Компоненты SoundPlayer и PictureBox представляют простые реализации асинхронной модели, основанной на событиях. Компоненты WebClient и BackgroundWorker представляют более сложные реализации асинхронной модели, основанной на событиях.

Ниже приведен пример объявления класса, который соответствует шаблону:

Public Class AsyncExample
    ' Synchronous methods.
    Public Function Method1(ByVal param As String) As Integer 
    Public Sub Method2(ByVal param As Double) 

    ' Asynchronous methods.
    Overloads Public Sub Method1Async(ByVal param As String) 
    Overloads Public Sub Method1Async(ByVal param As String, ByVal userState As Object) 
    Public Event Method1Completed As Method1CompletedEventHandler

    Overloads Public Sub Method2Async(ByVal param As Double) 
    Overloads Public Sub Method2Async(ByVal param As Double, ByVal userState As Object) 
    Public Event Method2Completed As Method2CompletedEventHandler

    Public Sub CancelAsync(ByVal userState As Object) 

    Public ReadOnly Property IsBusy () As Boolean

    ' Class implementation not shown.
End Class
public class AsyncExample
{
    // Synchronous methods.
    public int Method1(string param);
    public void Method2(double param);

    // Asynchronous methods.
    public void Method1Async(string param);
    public void Method1Async(string param, object userState);
    public event Method1CompletedEventHandler Method1Completed;

    public void Method2Async(double param);
    public void Method2Async(double param, object userState);
    public event Method2CompletedEventHandler Method2Completed;

    public void CancelAsync(object userState);

    public bool IsBusy { get; }

    // Class implementation not shown.
}

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

Асинхронные перегрузки метода

Существуют две возможных перегрузки асинхронных операций: с одним вызовом и с несколькими вызовами. Можно отличить эти две формы посредством сигнатур этих методов: форма с несколькими вызовами содержит дополнительный параметр userState. Эта форма позволяет коду вызывать Method1Async(string param, object userState) несколько раз без ожидания завершения любых асинхронных операций в очереди. С другой стороны, если попытаться вызвать Method1Async(string param) до завершения предыдущего вызова, метод создаст исключение InvalidOperationException.

Параметр userState для перегрузок с несколькими вызовами позволяет различать асинхронные операции. Для каждого вызова Method1Async(string param, object userState) следует предоставить уникальное значение (например, GUID или хэш-код), затем при завершении каждой операции обработчик события может определить, какой экземпляр операции привел к созданию события завершения.

Отслеживание операций в очереди

При использовании перегрузок с несколькими вызовами код нуждается в отслеживании объектов userState (идентификаторов задач) для задач в очереди. При каждом вызове Method1Async(string param, object userState), как правило, будет создаваться новый, уникальный объект userState и добавляться в коллекцию. Если задача, соответствующая этому объекту userState, создает событие завершения, реализация метода завершения просмотрит свойство AsyncCompletedEventArgs.UserState и удалит его из коллекции. Используемый таким образом параметр userState берет на себя роль идентификатора задачи.

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

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

Отмена операций в очереди

Важно иметь возможность отменять асинхронные операции в любое время до их завершения. Классы, реализующие асинхронную модель на основе событий, будут содержать метод CancelAsync (если существует только один асинхронный метод) или имя_методаAsyncCancel (если существует несколько асинхронных методов).

Методы, разрешающие несколько вызовов, принимают параметр userState, который можно использовать для отслеживания времени существования каждой задачи. Метод CancelAsync принимает параметр userState, который позволяет отменять определенные задачи в очереди.

Методы, поддерживающие только одну операцию в очереди, такие как Method1Async(string param), не поддерживают отмену.

Получение обновлений хода выполнения и добавочных результатов

Класс, который соответствует асинхронной модели, основанной на событиях, может дополнительно предоставить событие для отслеживания хода выполнения и добавочных результатов. Как правило, он будет называться ProgressChanged или имя_методаProgressChanged, а его соответствующий обработчик события будет принимать параметр ProgressChangedEventArgs.

Обработчик события ProgressChanged может просмотреть свойство ProgressChangedEventArgs.ProgressPercentage для определения процентного соотношения выполненной части асинхронной задачи. Это свойство может находиться в диапазоне от 0 до 100, оно может использоваться для обновления свойства Value объекта ProgressBar. Если имеется несколько асинхронных операций в очереди, можно использовать свойство ProgressChangedEventArgs.UserState для определения, какая операция предоставляет отчет о ходе выполнения.

Некоторые классы могут предоставлять добавочные результаты при выполнении асинхронных операций. Эти результаты будут сохраняться в классе, производном из ProgressChangedEventArgs, они будут появляться как свойства в производном классе. Доступ к этим результатам можно получить в обработчике события ProgressChanged так же, как можно получить доступ к свойству ProgressPercentage. Если в очереди находятся несколько асинхронных операций, можно использовать свойство UserState для определения операции, которое оповестило о добавочных результатах.

См. также

Задачи

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

Практическое руководство. Фоновое выполнение операции

Практическое руководство. Реализация формы, в которой выполняется фоновая операция

Ссылки

ProgressChangedEventArgs

BackgroundWorker

AsyncCompletedEventArgs

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

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

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

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

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