Поделиться через


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

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

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

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

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

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

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

Класс, поддерживающий асинхронный шаблон на основе событий, будет иметь один или несколько методов с именем MethodNameAsync. Эти методы могут копировать синхронные версии, выполняющие ту же операцию в текущем потоке. Класс также может иметь событие MethodNameCompleted , и он может иметь метод MethodNameAsyncCancel (или просто CancelAsync).

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

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

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

Осторожность

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

Характеристики асинхронного шаблона на основе событий

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

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

Примеры асинхронного шаблона на основе событий

Компоненты 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 для перегрузок с несколькими вызовами позволяет различать асинхронные операции. Вы предоставляете уникальное значение (например, guid или хэш-код) для каждого вызова Method1Async(string param, object userState), а при завершении каждой операции обработчик событий может определить, какой экземпляр операции вызвал событие завершения.

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

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

Замечание

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

Отмена ожидающих операций

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

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

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

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

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

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

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

См. также