并发事件: 实现CLR异步编程模型2011-10-24 msdn Jeffrey Richter通常 I/O 操作的特点是速度慢、不可预见。当应用程序执行同步 I/O 操作时,基本上会放弃对正在 完成实际工作的设备的控制。例如,如果应用程序调用 StreamRead 方法从 FileStream 或 NetworkStream 读取某些字节,我们无法预计该方法需要多长时间才能返回。如果正在被读取的文件位于 本地硬盘上,那么 Read 操作可能会立即返回。如果存储文件的远程服务器脱机,则 Read 方法可能会等 待几分钟,然后超时并引发异常。在此期间,发出同步请求的线程会被占用。如果该线程是 UI 线程,则 应用程序将被冻结并停止响应用户的输入。正在等待同步 I/O 完成的线程受阻,意味着该线程虽 然空闲,但无法执行有用工作。为提高可伸缩性,许多应用程序开发人员会创建更多线程。但遗憾的是, 每个线程都会带来相当大的管理开销,如其内核对象、用户模式和内核模式堆栈、增加的环境切换、调用 带有线程附加/分离通知的 DllMain 方法等。最终的结果是可伸缩性被降低了。如果应用程序希 望保持对用户的响应能力、提高可伸缩性和吞吐量并提高可靠性,则不应同步执行 I/O 操作。该应用程 序应使用公共语言运行库 (CLR) 异步编程模型 (APM) 来执行异步 I/O 操作。关于如何使用 CLR APM 有 许多书面资料,其中包括我的著作《CLR via C#》第二版 (Microsoft Press®, 2006) 的第 23 章。 但我没有注意到有哪个资料解释了如何定义一个类,提供用于实施 APM 的方法。因此我决定在此专栏中 着重关注这一主题。开发人员希望实施 APM 基本上有四个原因。第一,您可能要构建一个类,用 于直接与硬件(如硬盘上的文件系统、网络、串行端口或并行端口)进行通信。正如上面提到的,设备 I/O 是不可预见的,因此应用程序在与硬件通信时应该执行异步 I/O 操作,以使应用程序保持响应能力 、伸缩性和可靠性。幸运的是,Microsoft® .NET Framework 已经包含了与许多硬件设备通 信的类。因此,除非您要定义一个用于与 Framework 类库 (FCL) 不支持的硬件设备(如并行端口)进行 通信的类,否则不需要亲自实施 APM。可是尽管 FCL 支持某些设备,但不支持其中的某些特定子功能。 在这种情况下,如果您希望执行 I/O 操作,则可能需要实施 APM。例如,FCL 虽然提供了允许应用程序 与磁盘进行通信的 FileStream 类,但 FileStream 不允许您访问伺机锁定 (microsoft.com/msj/0100/win32/win320100.aspx)、稀疏文件流 (microsoft.com/msj/1198/ntfs/ntfs.aspx) 或 NTFS 文件系统提供的其他新奇的功能。如果要编写 P/Invoke 包装来调用提供这些功能的 Win32® API,那么您可能会希望包装支持 APM,从而可以异步 执行操作。第二,您可能要在一个已经定义的与硬件直接通信的类上构建一个抽象层。.NET Framework 中已经提供了几个这样的示例。例如,假设您要将数据发送给一个 Web 服务方法。在 Web 服 务客户端代理类上,有一个方法可接受您的参数,这些参数可能是一个复杂的数据结构集。在内部,该方 法将这些复杂的数据结构序列化为一个字节数组。然后使用 NetworkStream 类(该类已经具备了使用异 步 I/O 与硬件通信的能力)通过网络发送该字节数组。另一个示例出现在访问数据库时。ADO.NET SqlCommand 类型提供了 BeginExecuteNonQuery、BeginExecuteReader 和其他 BeginXxx 方法,这些方 法可以分析参数,以便将数据通过网络发送到数据库。在 I/O 完成时,会调用相应的 EndExecuteNonQuery、EndExecuteReader 和其他 EndXxx 方法。在内部,这些 EndXxx 方法分析得到的 数据并将富数据对象返回给调用方。第三,您的类可能会提供一种方法,这种方法执行起来可能 需要很长时间。在这种情况下,您可能希望提供 BeginXxx 和 EndXxx 方法,为调用方提供方便的 APM。 前面的示例最终是 I/O 密集型操作,与之不同,这次您的方法是执行计算密集型操作。由于是计算密集 型操作,因此必须使用一个线程来执行该工作;定义 BeginXxx 和 EndXxx 方法只是为了方便您的类用户 。