日期:2008-03-25  浏览次数:20421 次

Aiyiweb.Com提示:曾不断赞扬异步编程模型 (APM) 的优点,强调异步执行 I/O 密集型操作是生产高呼应和可伸缩使用程序及组件的关键。这些目标是可以达成的,由于 APM 可让您使用极少量的线程来执行大量的任务,而无需阻止任何线程。遗憾的是,使用 APM 构建典型的使用程序或组件有些难度,

  曾不断赞扬异步编程模型 (APM) 的优点,强调异步执行 I/O 密集型操作是生产高呼应和可伸缩使用程序及组件的关键。这些目标是可以达成的,由于 APM 可让您使用极少量的线程来执行大量的任务,而无需阻止任何线程。遗憾的是,使用 APM 构建典型的使用程序或组件有些难度,因此许多程序员不情愿去做。

  有几个要素使得 APM 难以实现。首先,您需求避免形状数据驻留于线程的堆栈上,由于线程堆栈上的数据不能迁移到其他线程。避免基于堆栈的数据意味着必须避免方法参数和局部变量。多年以来,开发人员已喜欢上参数和局部变量,由于它们使编程变得简单多了。

  其次,您需求将代码拆分为几个回调方法,有时称为“续”。例如,如果在一个方法中开始异步读取或写入操作,之后必须实现要调用的另一个方法(可能通过不同的线程),以处理 I/O 操作的结果。但程序员就是不习惯考虑以这种方法进行数据处理。在方法和线程之间迁移形状意味着形状必须进行打包,导致实现过程复杂化。

  再次,众多有用的编程结构 — 如 try/catch/finally 语句、lock 语句、using 语句,甚至是循环(for、while 和 foreach 语句)— 不能在多个方法之间进行拆分。避免这些结构也添加了实现过程的复杂性。

  最后,尝试提供多种功用,如协调多个堆叠操作的结果、取消、超时,以及将 UI 控件修正封送到 Windows® 窗体或 Windows Presentation Foundation (WPF) 使用程序中的 GUI 线程,这都为使用 APM 添加了更多的复杂性。

  在本期的专栏中,我将演示 C# 编程言语的一些最新添加内容,它们大大简化了异步编程模型的使用。之后我会引见我本人的一个类,称为 AsyncEnumerator,它建立在这些 C# 言语功用的基础上,用来处理我刚提到的问题。我的 AsyncEnumerator 类能够让您在代码中使用 APM 变得简单而风趣。通过此类,您的代码会变得可伸缩且高呼应,因此没有理由不使用异步编程。请留意,AsyncEnumerator 类是 Power Threading 库的一部分,并且依赖于同样是此库一部分的其他代码;读者可从 Wintellect.com 下载该库。

  匿名方法和 lambda 表达式

  SynchronousPattern 方法显示了如何同步打开并读取文件。该方法简单明了;它会结构一个 FileStream 对象,分配 Byte[],调用 Read,然后处理前往的数据。C# using 语句可确保完成数据处理后会关闭该 FileStream 对象。

  ApmPatternWithMultipleMethods 方法显示了如何使用公共言语运转时 (CLR) 的 APM,来执行与 SynchronousPattern 方法相反的操作。您会立即看到实现过程要复杂得多。请留意,ApmPatternWithMultipleMethods 方法会启动异步 I/O 操作,操作完成时会调用 ReadCompleted 方法。同时请留意,两个方法之间的数据传递是通过将共享数据封装到 ApmData 类的实例来完成的,为此我必须专门进行定义,以便启用这两个方法之间的数据传递。还应留意,不能使用 C# using 语句,由于 FileStream 是在一个方法中打开,然后在另一个方法中关闭的。为弥补这个问题,我编写了代码,用于在 ReadCompleted 方法前往之前显式调用 FileStream 的 Close 方法。

  ApmPatternWithAnonymousMethod 方法展现了如何使用 C# 2.0 称为匿名方法的新功用重新编写此代码,通过此功用您可以将代码作为参数传递到方法。它能无效地让您将一个方法的代码嵌入到另一个方法的代码中。(我在所著书籍“CLR via C#”(CLR 编程之 C# 篇)(Microsoft Press, 2006) 中详细说明了匿名方法。)请留意,ApmPatternWithAnonymousMethod 方法要简短得多,也更易于理解 — 在习惯使用匿名方法后就可以体会到这一点。

  首先,请留意该代码较简单,由于它完全包含在一个方法内。在此代码中,我将调用 BeginRead 方法启动异步 I/O 操作。所有 BeginXxx 方法会将其第二个至最后一个参数视为一个援用方法的委托,即 AsyncCallback,该方法在操作完成时由线程池线程进行调用。通常,使用 APM 时,您必须编写单独的方法,为该方法命名,并通过 BeginXxx 方法的最后一个参数将额外数据传递到该方法。但是,匿名方法功用允许只编写单独的内嵌方法,这样启动请求和处理结果的所有代码便会和环境协调。实际上,该代码看上去与 SynchronousPattern 方法有些类似。

  其次,请留意 ApmData 类不再是必需的;您不需求定义该类、结构其实例以及使用它的任何字段!这是如何实现的?其实,匿名方法的作用不只仅限于将一个方法的代码嵌入另一个方法的代码中。当 C# 编译器检测到外部方法中声明的任何参数或局部变量也用于内部方法时,该编译器实际上会自动定义一个类,并且两个方法之间共享的每个变量会成为此编译器定义的类中的字段。然后,在 ApmPatternWithAnonymousMethod 方法内,编译器会生成代码以结构此类的实例,且援用变量的任何代码都会编译成访问编译器所定义类的字段的代码。编译器还使得内部方法成为新类上的实例方法,允许其代码轻松地访问字段,如今两个方法可以共享数据。

  这是匿名方法的出色功用,它可让您像使用方法参数和局部变量一样编写代码,但实际上编译器会重新编写您的代码,从堆栈中取出这些变量,并将它们作为字段嵌入对象。对象可在方法之间轻松传递,并且可以从一个线程轻松迁移到另一个线程,这对于使用 APM 而言是十分完满的。由于编译器会自动执行所有的任务,您可以很轻松地将最后一个参数的空值传递到 BeginRead 方法,由于如今没有要在方法和线程之间显式传递的数据。但是,我仍然无法使用 C# using 语句,由于此处有两个不同的方法,虽然看上去似乎只要一个方法。

  以下内容显示了执行图 1 中摘录的代码后的输出。

  Primary ThreadId=1
  ThreadId=1: 4D-5A-90-00-03 (SynchronousPattern)
  ThreadId=3: 4D-5A-90-00-03 (ApmPatternWithMultipleMethods)
  ThreadId=3: 4D-5A-90-00-03 (ApmPatternWithAnonymousMethod)
  ThreadId=3: 4D-5A-90-00-03 (ApmPatternWithLambdaExpression)
  ThreadId=3: 4D-5A-90-00-03 (ApmPatternWithIterator)

  我让 Main 方法显示使用程序主线程的托管线程 ID。然后我让 ProcessData 方法显示执行该方法的线程的托管线程 ID。如您所见,输出显示了所有异步模式让主线程之外的其他线程执行结果,而同步模式则让使用程序的主线程执行所有任务。

  还应指出,C# 3.0 引入了一个新功用,称为 lambd