基于事件的异步模式概述

同时执行许多任务但对用户交互保持响应的应用程序通常需要使用多个线程的设计。 该 System.Threading 命名空间提供了创建高性能多线程应用程序所需的所有工具,但实际上使用这些工具需要对多线程软件工程具有重大经验。 对于相对简单的多线程应用程序,该 BackgroundWorker 组件提供了一个简单的解决方案。 对于更复杂的异步应用程序,请考虑实现遵循基于事件的异步模式的类。

基于事件的异步模式使多线程应用程序具有优势,同时隐藏多线程设计固有的许多复杂问题。 使用支持此模式的类可以:

  • 在后台执行耗时的任务,例如下载和数据库操作,不会中断应用程序。

  • 同时执行多个操作,每次完成时接收通知。

  • 等待资源变得可用,但不会停止(“阻止”)你的应用程序。

  • 使用熟悉的事件和委托模型与挂起的异步操作通信。 有关使用事件处理程序和委托的详细信息,请参阅 事件

支持基于事件的异步模式的类将具有一个或多个名为 MethodNameAsync 的方法。 这些方法可能会创建同步版本的镜像,这些同步版本会在当前线程上执行相同的操作。 该类可能还具有 MethodNameCompleted 事件,并且可能具有 MethodNameAsyncCancel (或只是 CancelAsync)方法。

PictureBox 是支持基于事件的异步模式的典型组件。 可以通过调用其 Load 方法同步下载图像,但如果图像很大,或者网络连接速度较慢,应用程序将停止响应,直到下载操作完成并调用的 Load 返回。

如果希望应用程序在加载映像时继续运行,可以调用 LoadAsync 该方法并处理 LoadCompleted 事件,就像处理任何其他事件一样。 调用 LoadAsync 该方法时,应用程序将继续运行,而下载将在单独的线程(“在后台”)上继续运行。 当图像加载作完成时,将调用事件处理程序,事件处理程序可以检查 AsyncCompletedEventArgs 参数以确定下载是否已成功完成。

基于事件的异步模式要求可以取消异步作,并且 PictureBox 控件使用其 CancelAsync 方法支持此要求。 调用 CancelAsync 以提交请求停止挂起的下载,当任务被取消时,LoadCompleted 事件将会被触发。

谨慎

下载可能会像发出请求一样 CancelAsync 完成,因此 Cancelled 可能不会反映取消请求。 这称为 争用条件 ,是多线程编程中的常见问题。 有关多线程编程中的问题的详细信息,请参阅 托管线程处理最佳做法

基于事件的异步模式的特征

基于事件的异步模式可能采用多种形式,具体取决于特定类支持的操作的复杂性。 最简单的类可能具有单个 MethodNameAsync 方法和相应的 MethodNameCompleted 事件。 更复杂的类可能有多个 MethodNameAsync 方法,每个方法都有相应的 MethodNameCompleted 事件,以及这些方法的同步版本。 类可以选择性地支持异步操作中的取消、进度报告和增量结果。

异步方法还可能支持多个挂起调用(多个并发调用),允许你的代码在未完成其他挂起操作之前多次调用它。 正确处理这种情况可能需要您的应用程序跟踪每个操作的完成情况。

基于事件的异步模式示例

SoundPlayerPictureBox组件代表基于事件的异步模式的简单实现。 和WebClientBackgroundWorker组件表示基于事件的异步模式的更复杂的实现。

下面是符合模式的示例类声明:

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 对象(任务 ID)以处理挂起的任务。 对于每次调用 Method1Async(string param, object userState),通常会生成一个新的唯一 userState 对象,并将其添加到集合中。 当与此对象对应的 userState 任务引发完成事件时,完成方法实现将检查 AsyncCompletedEventArgs.UserState 并将其从集合中删除。 这样,参数 userState 将扮演任务 ID 的角色。

注释

在为你对多调用重载的调用中的 userState 提供唯一值时,一定要小心。 如果任务 ID 不唯一,将导致异步类引发 ArgumentException

取消挂起的操作

在完成之前,能够随时取消异步操作是非常重要的。 实现基于事件的异步模式的类将具有一个方法(如果只有一个 CancelAsync 异步方法)或 MethodNameAsyncCancel 方法(如果有多个异步方法)。

允许多个调用的方法采用一个 userState 参数,可用于跟踪每个任务的生存期。 CancelAsync 采用一个 userState 参数,该参数允许取消特定的挂起任务。

一次只支持一个挂起操作的方法(如 Method1Async(string param))是不可取消的。

接收进度更新和增量结果

遵循基于事件的异步模式的类可以选择性地提供用于跟踪进度和增量结果的事件。 这通常命名 ProgressChangedMethodNameProgressChanged,其相应的事件处理程序将采用参数 ProgressChangedEventArgs

事件的事件处理程序 ProgressChanged 可以检查 ProgressChangedEventArgs.ProgressPercentage 属性以确定异步任务已完成的百分比。 此属性的范围为 0 到 100,可用于更新 Value a ProgressBar的属性。 如果有多个异步操作挂起,你可以使用 ProgressChangedEventArgs.UserState 属性来分辨出哪个操作在报告进度。

某些类可能会在异步操作进行时报告增量结果。 这些结果将存储在派生自 ProgressChangedEventArgs 的类中,它们将显示为派生类中的属性。 可以在事件处理程序 ProgressChanged 中访问这些结果,就像访问 ProgressPercentage 属性一样。 如果有多个异步操作挂起,你可以使用 UserState 属性来分辨出哪个操作在报告增量结果。

另请参阅