实现基于事件的异步模式

如果您正使用一些可能导致显著的延迟的操作编写类,请考虑通过实现 基于事件的异步模式概述 向类提供异步功能。

基于事件的异步模式提供了一个打包具有异步功能的类的标准化方式。 如果使用像 AsyncOperationManager 这样的帮助器类来实现类,则您的类将在所有应用程序模型(包括 ASP.NET、控制台应用程序和 Windows 窗体应用程序)下正常运行。

有关实现基于事件的异步模式的示例,请参见如何:实现支持基于事件的异步模式的组件

对于简单的异步操作,您可以找到合适的 BackgroundWorker 组件。 有关 BackgroundWorker的更多信息,请参见如何:在后台运行操作

下面的列表描述了本主题中讨论的基于事件的异步模式的功能。

  • 实现基于事件的异步模式的可能性

  • 指定异步方法

  • 可以选择支持取消

  • 可以选择支持 IsBusy 属性

  • 可以选择提供对进度报告的支持

  • 可以选择提供对返回增量结果的支持

  • 处理方法中的 Out 和 Ref 参数

实现基于事件的异步模式的可能性

考虑在以下情况下实现基于事件的异步模式:

  • 类的客户端不需要可用于异步操作的 WaitHandleIAsyncResult 对象,这意味着轮询和 WaitAllWaitAny 将需要由客户端生成。

  • 您希望由客户端使用人们熟悉的事件/委托模型来管理异步操作。

任何操作都是异步实现的一个候选项,但应考虑到那些您期望的操作可能会导致长时间的延迟。 如果客户端调用了某个方法,在完成时收到了通知,并且不需要进一步的干预,则这样的客户端中的操作便尤为合适。 连续运行、定期地向客户端报告进度、增量结果或状态更改的操作也是合适的操作。

有关决定何时支持基于事件的异步模式的更多信息,请参见确定何时实现基于事件的异步模式

指定异步方法

对于您要向其提供异步等效方法的各个同步方法 方法名称:

定义一个满足以下条件的 方法名称Async 方法:

  • 返回 void。

  • 采用与 方法名称 方法相同的参数。

  • 接受多个调用。

也可以定义一个 方法名称Async 重载,让该重载与 方法名称Async 相同,但是让它带有一个附加的对象赋值的参数(即 userState)。 如果您准备管理方法的多个并发调用,可进行此操作,在这种情况下,userState 值将发送回所有事件处理程序以便区分对该方法发出的各个调用。 不过,您也可以纯粹为了获得一个供以后检索用的用户状态来执行此操作。

对于各个单独的 方法名称Async 方法签名:

  1. 在与方法相同的类中定义以下事件:

    Public Event MethodNameCompleted As MethodNameCompletedEventHandler
    
    public event MethodNameCompletedEventHandler MethodNameCompleted;
    
  2. 定义以下委托和 AsyncCompletedEventArgs。 可能会在类的外部定义下面的委托和参数,但是会在同一命名空间中进行定义。

    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; }
    }
    
    • 确保 方法名称CompletedEventArgs 类将其成员公开为只读属性而不是字段,因为字段会阻止数据绑定。

    • 不要为不产生结果的方法定义任何 AsyncCompletedEventArgs 派生类。 只使用 AsyncCompletedEventArgs 本身的实例即可。

      注意注意

      如果重用委托和 AsyncCompletedEventArgs 类型是可行并且适当的,则可以接受这样的做法。在这种情况下,命名将和方法名不一致,因为给定的委托和 AsyncCompletedEventArgs 不依赖于单一方法。

可以选择支持取消

如果类将支持取消异步操作,则应按照下文所述向客户端公开取消方法。 请注意,在定义取消支持之前需做出两个决定:

  • 您的类(包括其将来预计的添加)是否仅有一个支持取消的异步操作?

  • 支持取消的异步操作是否能支持多个挂起操作? 即 方法名称Async 方法是否采用 userState 参数?它是否允许在等待任何操作完成之前进行多个调用?

使用下表中这两个问题的答案来确定您的取消方法应采用何种签名。

Visual Basic

 

支持同时执行多个操作

一次只能执行一个操作

整个类执行一个异步操作

Sub MethodNameAsyncCancel(ByVal userState As Object)
Sub MethodNameAsyncCancel()

类中执行多个异步操作

Sub CancelAsync(ByVal userState As Object)
Sub CancelAsync()

C#

 

支持同时执行多个操作

一次只能执行一个操作

整个类执行一个异步操作

void MethodNameAsyncCancel(object userState);
void MethodNameAsyncCancel();

类中执行多个异步操作

void CancelAsync(object userState);
void CancelAsync();

如果您定义了 CancelAsync(object userState) 方法,则客户端必须小心选择它们的状态值,使其能够区分该对象上调用的所有异步方法,而不是只区分单个异步方法的所有调用。

命名单个异步操作版本 方法名称AsyncCancel 的决定基于能在类似于 Visual Studio 的 IntelliSense 的设计环境中更轻松地发现方法。 这用于将相关成员分组并将它们从其他与异步功能无关的成员中区分出来。 如果您期望向后面的版本中添加其他的异步操作,最好定义 CancelAsync。

请不要从上表中的同一类定义多个方法。 这不容易弄明白,或者由于方法的增殖,类接口将变得混乱。

这些方法通常将立即返回,且该操作可能会实际取消,也可能不会。 在 方法名称Completed 事件的事件处理程序中,方法名称CompletedEventArgs 对象包含一个 Cancelled 字段,客户端可使用该字段来确定是否发生了取消。

遵守 实现基于事件的异步模式的最佳做法 中所述的取消语义。

可以选择支持 IsBusy 属性

如果类不支持多个并发调用,请考虑公开 IsBusy 属性。 这允许开发人员确定 方法名称Async 方法是否不用捕获 方法名称Async 方法中的异常就能运行。

遵守 实现基于事件的异步模式的最佳做法 中所述的 IsBusy 语义。

可以选择提供对进度报告的支持

我们经常希望异步操作可以在其操作期间报告进度。 基于事件的异步模式提供了实现此目的的准则。

  • 可以选择定义由异步操作引发的且在适当线程上调用的事件。 ProgressChangedEventArgs 对象带有整数值进度指示,应在 0 到 100 之间。

  • 按照以下准则命名此事件:

    • 如果类具有多个异步操作(或期望在将来的版本中发展为包括多个异步操作),则为 ProgressChanged;

    • 如果类具有一个异步操作,则为 MethodNameProgressChanged。

    此命名选择与为取消方法所做的选择相同,如“可以选择支持取消”一节中所述。

此事件应使用 ProgressChangedEventHandler 委托签名和 ProgressChangedEventArgs 类。 或者,如果能提供更多的域特定的进度指示(例如,下载操作的读取的字节数和总字节数),则您应定义 ProgressChangedEventArgs 的一个派生类。

请注意,无论该类支持几个异步方法,都只有该类的一个 ProgressChanged 或 方法名称ProgressChanged 事件。 客户端需要使用传递给 方法名称Async 方法的 userState 对象,以便区分多个并发操作上的进度更新。

可能存在多个操作支持进度且各个操作为进度返回不同的指示的情况。 在这种情况下,单个 ProgressChanged 事件不合适,您可能要考虑支持多个 ProgressChanged 事件。 在这种情况下,为每个 方法名称Async 方法使用 方法名称ProgressChanged 的命名模式。

遵循 实现基于事件的异步模式的最佳做法 中所述的进度报告语义。

可以选择提供对返回增量结果的支持

有时异步操作可以在完成之前返回增量结果。 有许多选项可用来支持此方案。 下面是一些示例。

单一操作类

如果您的类只支持一个异步操作,并且该操作能够返回增量结果,则:

  • 扩展 ProgressChangedEventArgs 类型以承载增量结果数据,并使用此扩展数据定义 方法名称ProgressChanged 事件。

  • 存在要报告的增量结果时,引发此 方法名称ProgressChanged 事件。

此解决方案特别适用于单异步操作类,因为发生的同一事件可以返回“所有操作”上的增量结果,与 方法名称ProgressChanged 事件的作用相同。

使用同类增量结果的多操作类

在这种情况下,您的类支持多个异步方法,每个方法都能够返回增量结果,并且这些增量结果都具有相同的数据类型。

遵循上述单一操作类模型,因为同一 EventArgs 结构将用于所有增量结果。 定义一个 ProgressChanged 事件,而不是 方法名称ProgressChanged 事件,因为它适用于多个异步方法。

使用异类增量结果的多操作类

如果您的类支持多个异步方法,每个异步方法返回不同类型的数据,您应该:

  • 将您的增量结果报告与您的进度报告分开。

  • 使用适当的 EventArgs 为每个异步方法定义一个单独的 方法名称ProgressChanged 事件以处理该方法的增量结果数据。

按照 实现基于事件的异步模式的最佳做法 中所述,在适当的线程上调用事件处理程序。

处理方法中的 Out 和 Ref 参数

虽然在 .NET Framework 中通常不提倡使用 out 和 ref,但如果要使用,需遵循以下规则:

给定同步方法 方法名称:

  • 方法名称 的 out 参数不应是 方法名称Async 的一部分。 它们应是与 方法名称 中的等效参数使用相同名称的 方法名称CompletedEventArgs 的一部分(除非有更合适的名称)。

  • 方法名称 的 ref 参数应显示为 方法名称Async 的一部分,并且是与 方法名称 中的等效参数使用相同名称的 方法名称CompletedEventArgs 的一部分(除非有更合适的名称)。

例如,给定:

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 类将如下所示:

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; };
}

请参见

任务

如何:实现支持基于事件的异步模式的组件

如何:在后台运行操作

如何:实现使用后台操作的窗体

参考

ProgressChangedEventArgs

AsyncCompletedEventArgs

概念

确定何时实现基于事件的异步模式

实现基于事件的异步模式的最佳做法

其他资源

使用基于事件的异步模式进行多线程编程