MSDN搬运 之 [基于事件的异步模式]

Posted 绣春刀

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MSDN搬运 之 [基于事件的异步模式]相关的知识,希望对你有一定的参考价值。

基于事件的异步模式概述

那些同时执行多项任务、但仍能响应用户交互的应用程序通常需要实施一种使用多线程的设计方案。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 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 对象的任务引发完成事件时,您的完成方法实现将检查 System.ComponentModel.AsyncCompletedEventArgs.UserState 并将此对象从集合中删除。在以这种方式使用时,userState 参数充当任务 ID 的角色。

技术分享注意

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

取消挂起的操作

我们必须能够在异步操作完成之前随时取消它们,这一点很重要。实现基于事件的异步模式的类将有一个 CancelAsync 方法(如果有多个异步方法)或 MethodNameAsyncCancel 方法(如果只有一个异步方法)。

允许多个调用的方法采用 userState 参数,此参数可用来跟踪各个任务的生存期。CancelAsync 采用 userState 参数,此参数允许您取消特定的挂起任务。

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

接收进度更新和增量结果

符合基于事件的异步模式的类可以为跟踪进度和增量结果提供事件。此事件通常叫做 ProgressChangedMethodNameProgressChanged,它对应的事件处理程序会带有一个 ProgressChangedEventArgs 参数。

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

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

 

实现基于事件的异步模式

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

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

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

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

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

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

  • 指定异步方法

  • 可以选择支持取消

  • 可以选择支持 IsBusy 属性

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

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

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

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

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

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

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

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

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

指定异步方法

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

定义一个满足以下条件的 MethodNameAsync 方法:

  • 返回 void

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

  • 接受多个调用。

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

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

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

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

    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 参数?它是否允许在等待任何操作完成之前进行多个调用?

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

 

 

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

    • MethodName 如果类具有一个异步操作,则为 ProgressChanged

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

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

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

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

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

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

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

单一操作类

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

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

  • 存在要报告的增量结果时,引发此 MethodNameProgressChanged 事件。

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

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

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

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

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

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

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

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

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

处理方法中的 Out 和 Ref 参数

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

给定同步方法 MethodName

  • MethodNameout 参数不应是 MethodNameAsync 的一部分。它们应是与 MethodName 中的等效参数使用相同名称的 MethodNameCompletedEventArgs 的一部分(除非有更合适的名称)。

  • MethodNameref 参数应显示为 MethodNameAsync 的一部分,并且是与 MethodName 中的等效参数使用相同名称的 MethodNameCompletedEventArgs 的一部分(除非有更合适的名称)。

例如,给定:

 
public int MethodName(string arg1, ref string arg2, out string arg3);

您的异步方法及其 AsyncCompletedEventArgs 类将如下所示:

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

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

基于事件的异步模式提供了一种在类中使用熟悉的事件和委托语义公开异步行为的有效方法。若要实现基于事件的异步模式,您需要遵守一些特定的行为要求。下面几节描述了您在实现一个遵循基于事件的异步模式的类时应当考虑的要求和准则。

有关概述,请参见实现基于事件的异步模式

下面的列表说明了本主题讨论的最佳做法:

  • 必需的行为保证

  • 完成

  • 完成的事件和 EventArgs

  • 同时执行操作

  • 访问结果

  • 进度报告

  • IsBusy 实现

  • 取消

  • 错误和异常

  • 线程处理和上下文

  • 准则

必需的行为保证

若要实现基于事件的异步模式,您必须提供一些保证来确保类的行为正确且类的客户端能够依赖这种行为。

完成

成功完成操作、遇到错误或取消操作时,始终要调用方法名称Completed 事件处理程序。任何情况下,应用程序都不应遇到这样的情况:应用程序保持空闲状态,而操作却一直不能完成。此规则的一个例外是:异步操作本身被设计为永远不完成。

完成的事件和 EventArgs

对于每个单独的方法名称Async 方法,应用以下设计要求:

  • 在该方法所在类中定义一个方法名称Completed 事件。

  • 为自 AsyncCompletedEventArgs 类派生的方法名称Completed 事件定义一个 EventArgs 类和伴随的委托。默认类名称的格式应当是方法名称CompletedEventArgs

  • 确保 EventArgs 类是特定于方法名称方法的返回值的。当使用 EventArgs 类时,切勿要求开发人员强制转换结果。

    下面的代码示例分别演示了此项设计要求的合理实现和错误实现。

// Good design
private void Form1_MethodNameCompleted(object sender, xxxCompletedEventArgs e) 
{ 
    DemoType result = e.Result;
}

// Bad design
private void Form1_MethodNameCompleted(object sender, MethodNameCompletedEventArgs e) 
{ 
    DemoType result = (DemoType)(e.Result);
}

同时执行操作

  • 如果类支持多个并发调用,开发人员则可以通过定义带有对象赋值状态参数或任务 ID(名为 userSuppliedState)的方法名称Async 重载,分别跟踪每个调用。此参数应当始终是方法名称Async 方法的签名中的最后一个参数。

  • 如果类定义了带有对象赋值状态参数或任务 ID 的方法名称Async 重载,请确保使用该任务 ID 来跟踪操作的生存期,并确保将该任务 ID 返回到完成处理程序。有一些用来提供帮助的帮助器类。有关并发管理的更多信息,请参见演练:实现支持基于事件的异步模式的组件

  • 如果类定义了没有状态参数的方法名称Async 方法,而且它不支持多个并发调用,请确保任何在前一方法名称Async 调用完成之前调用方法名称Async 的尝试都会引发 InvalidOperationException

  • 一般来说,如果多次调用了不带 userSuppliedState 参数的方法名称Async 方法(会导致多个未处理的操作),则不要引发异常。如果类明显无法处理这种情况,则将引发异常,但可假定开发人员能够处理多个不可区分回调。

访问结果

  • 如果在执行异步操作期间出现错误,其结果应当不可访问。确保在 Error 不为 null 时访问 AsyncCompletedEventArgs 中的任何属性都会引发由 Error 引用的异常。AsyncCompletedEventArgs 类为达到此目的提供了 RaiseExceptionIfNecessary 方法。

  • 确保访问结果的任何尝试将引发 InvalidOperationException,指出该操作已被取消。请使用 System.ComponentModel.AsyncCompletedEventArgs.RaiseExceptionIfNecessary 方法来进行此项验证。

进度报告

  • 尽可能支持进度报告。这样可以使开发人员在使用您的类时能提供更好的应用程序用户体验。

  • 如果实现了一个 ProgressChanged/方法名称ProgressChanged 事件,请确保在引发了特定异步操作的方法名称Completed 事件之后,不会引发该操作的任何此类事件。

  • 如果正在填充标准的 ProgressChangedEventArgs,则请确保始终能够将 ProgressPercentage 解释为一个百分比。该百分比不必是一个精确值,但它应表示为百分数的形式。如果您的进度报告读数不能是一个百分数,则请从 ProgressChangedEventArgs 类中派生一个类并将 ProgressPercentage 保留为 0。避免使用非百分数的报告读数。

  • 请确保在应用程序生命周期中的适当时间在适当的线程上引发了 ProgressChanged 事件。有关更多信息,请参见“线程处理和上下文”一节。

IsBusy 实现

  • 如果您的类支持多个并发调用,则不要公开 IsBusy 属性。例如,XML Web services 代理不会公开 IsBusy 属性,因为它们支持异步方法的多个并发调用。

  • 在调用方法名称Async 方法之后,但在引发方法名称Completed 事件之前,IsBusy 属性应返回 true。否则它将返回 falseBackgroundWorkerWebClient 组件都是公开 IsBusy 属性的类的示例。

取消

  • 尽可能支持取消。这样可以使开发人员在使用您的类时能提供更好的应用程序用户体验。

  • 在发生取消时,设置 AsyncCompletedEventArgs 对象中的 Cancelled 标志。

  • 确保访问结果的任何尝试将引发 InvalidOperationException,指出该操作已被取消。请使用 System.ComponentModel.AsyncCompletedEventArgs.RaiseExceptionIfNecessary 方法来进行此项验证。

  • 请确保对取消方法发出的调用始终能够成功返回,而且从不引发异常。一般来说,客户端不会得到关于在任何给定时间是否真正取消了某个操作的通知,也不会得到关于以前发出的取消是否已经成功的通知。不过,应用程序在取消成功时总能得到通知,因为应用程序参与了完成状态。

  • 在取消操作时,应引发方法名称Completed 事件。

错误和异常

  • 捕获所有发生在异步操作中的异常并将 System.ComponentModel.AsyncCompletedEventArgs.Error 属性的值设置为该异常。

线程处理和上下文

为了使类正确运行,应当使用给定应用程序模型(包括 ASP.NET 和 Windows 窗体应用程序)的适当线程或上下文调用客户端事件处理程序,这一点很重要。我们提供了两个重要的帮助器类,以确保您的异步类在任何应用程序模型中都能正确运行,这两个帮助器类是 AsyncOperationAsyncOperationManager

AsyncOperationManager 提供了 CreateOperation 方法,该方法会返回一个 AsyncOperation方法名称Async 方法调用 CreateOperation,类使用返回的 AsyncOperation 跟踪异步任务的生存期。

若要向客户端报告进度、增量结果和完成,请调用 AsyncOperationPostOperationCompleted 方法。AsyncOperation 负责将对客户端事件处理程序的调用封送到适当的线程和上下文。

技术分享注意

如果您明确想违反应用程序模型的策略,但仍想获得使用基于事件的异步模式的其他好处,则您可以避开这些规则。例如,您可能希望在 Windows 窗体中进行操作的某个类是自由线程类。只要开发人员了解隐含的限制,您就可以创建自由线程类。控制台应用程序不会将 Post 调用的执行同步。这可导致 ProgressChanged 事件的引发顺序紊乱。如果您希望将 Post 调用的执行序列化,请实现和安装 System.Threading.SynchronizationContext 类。

有关使用 AsyncOperationAsyncOperationManager 启用异步操作的更多信息,请参见演练:实现支持基于事件的异步模式的组件

准则

  • 理论上,方法调用与方法调用之间应是相互独立的。您应当避免在使用调用时使用共享资源。如果在不同调用之间共享资源,则您需要在您的实现中提供一个适当的同步机制。

  • 建议不要进行需要客户端实现同步的设计。例如,您可以使用一个异步方法将全局静态对象作为参数来接收;这类方法的多个并发调用可能会导致数据损坏或死锁。

  • 如果您使用多调用重载(签名中的 userState)来实现某个方法,您的类将需要管理由一系列用户状态、任务 ID 及其相应的挂起操作构成的一个集合。应当使用 lock 区域保护此集合,因为各种调用都会在此集合中添加和移除 userState 对象。

  • 如果可能而且适合这样做,请考虑重用 CompletedEventArgs 类。在这种情况下,命名和方法名不一致,因为给定的委托和 EventArgs 类型不会仅与单独某个方法联系在一起。不过,强制开发人员强制转换从 EventArgs 上的一个属性中检索的值是绝对不可取的。

  • 如果您正在创建自 Component 派生的类,请不要实现和安装您自己的 SynchronizationContext 类。所使用 SynchronizationContext 由应用程序模型而不是组件控制。

  • 当您使用任何一种多线程编程时,都有可能会遇到非常严重而复杂的 Bug。在实现任何使用多线程编程的解决方案之前,请参见托管线程处理的最佳做法

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

基于事件的异步模式提供了一种公开类的异步行为的模式。引入此模式后,.NET Framework 定义了两种公开异步行为的模式:基于 System.IAsyncResult 接口的异步模式和基于事件的模式。本主题介绍何时适合实现上述两种模式。

有关使用 IAsyncResult 接口进行异步编程的更多信息,请参见异步编程设计模式

一般原则

一般而言,您应该在所有可能的情况下使用基于事件的异步模式公开异步功能。但是,有些要求是基于事件的模式无法满足的。在这些情况下,除实现基于事件的模式外,可能还需要实现 IAsyncResult 模式。

技术分享注意

不实现基于事件的模式而只实现 IAsyncResult 模式的情况非常少见。

指南

下面的列表介绍有关应在何时实现基于事件的异步模式的指南:

  • 将基于事件的模式用作默认 API 以公开类的异步行为。

  • 当类主要用在客户端应用程序(例如 Windows 窗体)中时,不要公开 IAsyncResult 模式。

  • 仅在必须公开 IAsyncResult 模式才能满足要求时公开该模式。例如,需要与现有 API 兼容时可能需要公开 IAsyncResult 模式。

  • 不要在不公开基于事件的模式时公开 IAsyncResult 模式。

  • 如果必须公开 IAsyncResult 模式,应将其作为高级选项公开。例如,如果生成一个代理对象,则应默认生成基于事件的模式,其中具有一个生成 IAsyncResult 模式的选项。

  • IAsyncResult 模式实现上生成基于事件的模式实现。

  • 避免在同一个类上同时公开基于事件的模式和 IAsyncResult 模式。在“较高级别”的类上公开基于事件的模式,在“较低级别”的类上公开 IAsyncResult 模式。例如,比较 WebClient 组件上的基于事件的模式与 HttpRequest 类上的 IAsyncResult 模式。

    • 当为了提供兼容性需要在同一个类上公开基于事件的模式和 IAsyncResult 模式时,同时公开这两种模式。例如,如果已经释放了一个使用 IAsyncResult 模式的 API,则需要保留 IAsyncResult 模式以提供向后兼容性。

    • 如果得到的对象模型复杂性方面的优点大于分开实现的优点,则在同一个类上实现基于事件的模式和 IAsyncResult 模式。在一个类上同时公开两种模式比避免公开基于事件的模式效果更好。

    • 如果必须在同一个类上同时公开基于事件的模式和 IAsyncResult 模式,可使用设置为 AdvancedEditorBrowsableAttributeIAsyncResult 模式实现标记为高级功能。这指示设计环境(如 Visual Studio IntelliSense)不显示 IAsyncResult 属性和方法。这些属性和方法仍然是完全可用的,但使用 IntelliSense 的开发人员能够更清楚地查看 API。

在公开基于事件的模式的同时公开 IAsyncResult 模式的条件

在前面介绍的许多情形中,基于事件的异步模式具有诸多优点,但是,如果性能是最重要的考虑因素,则需要了解这种模式同时也具有一些缺点。

基于事件的模式不如 IAsyncResult 模式的情形有三种:

  • 被阻止,正在等待一个 IAsyncResult

  • 被阻止,正在等待多个 IAsyncResult 对象

  • 轮询 IAsyncResult 上的完成情形

您可以将基于事件的模式用于这些情形,但这种方式比使用 IAsyncResult 模式较为麻烦。

开发人员经常将 IAsyncResult 模式用于通常具有很高的性能要求的服务。例如,对完成情形的轮询就是一种高性能服务器技术。

此外,基于事件的模式不如 IAsyncResult 模式有效,因为它创建更多的对象(尤其是 EventArgs),而且它会在线程之间进行同步。

下表列出了决定使用 IAsyncResult 模式时应遵循的一些建议:

  • 仅在明确要求支持 WaitHandleIAsyncResult 对象时公开 IAsyncResult 模式。

  • 仅当已经具有使用 IAsyncResult 模式的现有 API 时公开 IAsyncResult 模式。

  • 如果已经具有基于 IAsyncResult 模式的现有 API,则应考虑在下一个版本中也公开基于事件的模式。

  • 仅当具有高性能要求,而且经过验证发现基于事件的模式无法满足此要求而 IAsyncResult 模式能够满足时,才公开 IAsyncResult 模式。

以上是关于MSDN搬运 之 [基于事件的异步模式]的主要内容,如果未能解决你的问题,请参考以下文章

MSDN搬运 之 [使用 IAsyncResult 调用异步方法]

MSDN搬运 之 [事件]

MSDN搬运 之 [泛型 - 1]

MSDN搬运 之 [编写流]

C# 基于事件的异步模式

Javascript异步编程方法之------“事件监听”