如果您正在编写一个包含某些操作的类,而这些操作可能会导致明显的延迟,请考虑通过实现基于事件的异步模式来为类提供异步功能。
基于事件的异步模式提供了一种标准化的方式来打包具有异步功能的类。 如果使用帮助程序类(例如 AsyncOperationManager)实现,则类将在任何应用程序模型中正常工作,包括 ASP.NET、控制台应用程序和 Windows 窗体应用程序。
有关实现基于事件的异步模式的示例,请参阅 如何:实现支持基于事件的异步模式的组件。
对于简单的异步操作,您可能会发现合适的 BackgroundWorker 组件。 有关BackgroundWorker的详细信息,请参阅如何:在后台运行操作。
以下列表介绍本主题中讨论的基于事件的异步模式的功能。
实现基于事件的异步模式的机会
命名异步方法
(可选)支持取消
(可选)支持 IsBusy 属性
(可选)提供对进度报告的支持
(可选)提供对返回增量结果的支持
处理方法中的 Out 和 Ref 参数
实现基于事件的异步模式的机会
考虑在以下情况下实现基于事件的异步模式:
类的客户端不需要WaitHandleIAsyncResult对象来进行异步操作,这意味着轮询和WaitAllWaitAny的实现需要由客户端来完成。
你希望客户端使用熟悉的事件/委托模型来管理异步操作。
任何操作都是异步实现的候选对象,但应考虑那些预期会产生长延迟的操作。 尤其适用于客户端调用方法并在完成后接收到通知的操作,无需进一步干预。 此外,还适合连续运行的操作,定期向客户端通知进度、增量结果或状态变化。
有关决定何时支持基于事件的异步模式的详细信息,请参阅 决定何时实现基于事件的异步模式。
命名异步方法
对于要为其提供异步对应项的每个同步方法 MethodName :
定义 MethodNameAsync 方法,该方法:
返回
void
。采用与 MethodName 方法相同的参数。
支持多次调用。
(可选)定义与 MethodNameAsync 完全相同的 MethodNameAsync 重载,但要额外添加对象赋值参数(即 )。userState
如果已准备好管理方法的多个并发调用(在这种情况下,userState
值将传递回所有事件处理程序以区分方法的调用),可使用此方法。 您也可以选择将其作为存储用户状态以供日后检索的地方。
对于每个单独的 MethodNameAsync 方法签名:
在方法所在的同一类中定义以下事件:
Public Event MethodNameCompleted As MethodNameCompletedEventHandler
public event MethodNameCompletedEventHandler MethodNameCompleted;
定义以下委托和 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; } }
确保 MethodNameCompletedEventArgs 类将其成员公开为只读属性,而不是字段,因为字段阻止数据绑定。
不要为不生成结果的方法定义任何 AsyncCompletedEventArgs派生类。 直接使用 AsyncCompletedEventArgs 本身的实例即可。
注释
在可行且适当的情况下,重复使用委托和 AsyncCompletedEventArgs 类型是完全可以接受的。 在这种情况下,命名会与方法名称不一致,因为给定委托和 AsyncCompletedEventArgs 不限于单个方法。
(可选)支持取消
如果您的类支持取消异步操作,应按如下所示向客户端提供取消功能。 定义取消支持之前,需要确定两点:
- 你的类(包括将来预计要添加的内容),是否只具有一个支持取消操作的异步操作?
- 支持取消的异步操作是否能支持多个挂起操作? 也就是说, MethodNameAsync 方法是否采用参数
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)
方法,客户端在选择状态值时必须谨慎,以便能够区分对象上调用的所有异步方法,而不是仅仅区分单个异步方法的所有调用。
将单异步操作版本 MethodNameAsyncCancel 命名的决定是为了能够在 Visual Studio 的 IntelliSense 等设计环境中更轻松地发现该方法。 这会对相关成员进行分组,并将其与与异步功能无关的其他成员区分开来。 如果预计后续版本中可能会添加异步操作,最好定义 CancelAsync
。
不要在同一类中定义上表中的多个方法。 这没有意义,否则会因为过多的方法使类接口混乱。
通常,这些方法会立即返回,并且操作实际上可能会/无法取消。 在 MethodNameCompleted 事件的事件处理程序中, MethodNameCompletedEventArgs 对象包含一个 Cancelled
字段,客户端可以使用该字段来确定是否发生了取消。
遵守在 实施基于事件的异步模式的最佳做法 中所述的取消的语义。
(可选)支持 IsBusy 属性
如果类不支持多个并发调用,请考虑公开属性 IsBusy
。 这样,开发人员就可以确定 MethodNameAsync 方法是否正在运行,而不会从 MethodNameAsync 方法捕获异常。
遵守 IsBusy
中实现基于事件的异步模式的最佳实践中描述的语义。
(可选)提供对进度报告的支持
通常期望异步操作在其操作期间报告进度。 基于事件的异步模式提供了实现该方法的准则。
(可选)定义由异步作引发并在相应线程上调用的事件。 该 ProgressChangedEventArgs 对象携带一个整数值进度指示器,该指示器应介于 0 和 100 之间。
将此事件命名如下:
ProgressChanged
如果类具有多个异步操作(或预期在未来版本中包括多个异步操作);如果类具有单个异步操作,则调用MethodNameProgressChanged。
该命名方法与命名取消方法(如“选择性地支持取消”部分所述)相同。
此事件应使用 ProgressChangedEventHandler 委托签名和 ProgressChangedEventArgs 类。 或者,如果可以提供更符合特定领域的进度指示器(例如,下载操作中的已读取字节数和总字节数),那么您应该定义ProgressChangedEventArgs的派生类。
请注意,该类只有一个 ProgressChanged
或 MethodNameProgressChanged 事件,无论它支持的异步方法数如何。 客户端应使用传递给 userState
Async 方法的 对象,以区分多个并发操作的进度更新。
在某些情况下,多个操作支持进度,并且每个操作返回不同的进度指示器。 在这种情况下,单个 ProgressChanged
事件不适用,可以考虑支持多个 ProgressChanged
事件。 在本例中,对每个 MethodNameAsync 方法使用 MethodNameProgressChanged 的命名模式。
遵守有关 实现基于事件的异步模式的最佳做法的进度报告语义。
(可选)提供对返回增量结果的支持
有时异步作可以在完成之前返回增量结果。 有许多选项可用于支持此方案。 下面是一些示例。
单一操作类
如果类仅支持单个异步作,并且该作能够返回增量结果,则:
扩展类型 ProgressChangedEventArgs 以携带增量结果数据,并使用此扩展数据定义 MethodNameProgressChanged 事件。
当有增量结果报告时,引发此 MethodNameProgressChanged 事件。
此解决方案特别适用于单一异步操作类,因为发生的同一事件可以对“所有操作”返回增量结果,与 MethodNameProgressChanged 事件一样。
使用同类增量结果的多操作类
在这种情况下,类支持多个异步方法,每个方法都能够返回增量结果,并且这些增量结果都具有相同的数据类型。
遵循上述用于单操作类的模型,因为同 EventArgs 结构适用于所有增量结果。 定义ProgressChanged
事件,而不是MethodNameProgressChanged事件,因为它适用于多个异步方法。
使用不同类增量结果的多操作类
如果类支持多个异步方法,则每个方法返回不同类型的数据,则应:
将增量结果报告与进度报告分开。
为每个异步方法定义一个单独的 MethodNameProgressChanged 事件,并使用适当的EventArgs处理该方法的增量结果数据。
根据实现 基于事件的异步模式的最佳做法中所述,在适当的线程上调用该事件处理程序。
处理方法中的 Out 和 Ref 参数
尽管在 .NET 中通常不建议使用 out
和 ref
,但当它们存在时要遵循以下规则:
给定同步方法 MethodName:
out
MethodName 的参数不应是 MethodNameAsync 的一部分。 相反,它们应是 MethodNameCompletedEventArgs 的一部分,其名称与其在 MethodName 中等效的参数相同(除非有更合适的名称)。ref
MethodName 的参数应显示为 MethodNameAsync 的一部分,并且作为 MethodNameCompletedEventArgs 的一部分,其参数与 MethodName 中等效的参数相同(除非有更合适的名称)。
例如,给定:
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; };
}