此实现解决了基于线程的低效解决方案的第 1 个问题:不必要的线程阻止。 对 DownloadStringAsync 的调用会立即返回,而不会阻止 UI 线程或线程池线程。 下载在后台执行,一旦下载完成,DownloadStringCompleted 事件将在相应线程上执行。
请注意,DownloadStringCompleted 事件处理程序在相应线程上执行,不需要 SynchronizationContext 代码,而基于线程的解决方案则需要此代码。 在后台,WebClient 自动捕获 SynchronizationContext 并接着将回调发布到该环境。 实现基于事件的模式的类通常可确保 Completed 处理程序在相应线程上执行。
基于事件的异步编程模式不会阻止没有必要阻止的线程,从这个角度讲该模式是高效的,而且它是 .NET Framework 中广泛使用的两种模式之一。 不过,基于事件的模式有几个限制:
- 该模式是非正式且仅仅依据惯例的,类可以偏离该模式。
- 将多个异步操作组合起来可能会相当困难,例如处理并行启动的异步操作或处理异步操作序列。
- 您无法轮询和检查异步操作是否已完成。
- 使用这些类型时必须十分小心。 例如,如果使用一个实例处理多个异步操作,则必须对注册事件处理程序进行编码,以便仅处理一个目标异步操作,即使多次调用该处理程序也是如此。
- 即使没有必要在 UI 线程上执行,也将始终在启动异步操作时捕获的 SynchronizationContext 上调用事件处理程序,从而导致额外的性能开销。
- 难以良好实现,并且需要定义多个类型(例如,事件处理程序或事件参数)。
图 2 列出了 .NET Framework 4 类的几个示例,这些类实现基于事件的异步模式。
图 2 .NET 类中基于事件的异步模式示例
类 |
操作 |
System.Activities.WorkflowInvoker |
InvokeAsync |
System.ComponentModel.BackgroundWorker |
RunWorkerAsync |
System.Net.Mail.SmtpClient |
SendAsync |
System.Net.NetworkInformation.Ping |
SendAsync |
System.Net.WebClient |
DownloadStringAsync |
IAsyncResult 模式
在 .NET 中实现异步操作的另一个惯例是 IAsyncResult 模式。 与基于事件的模型相比,IAsyncResult 是更高级的异步编程解决方案。
在 IAsyncResult 模式中,使用 Begin 和 End 方法公开异步操作。 可以调用 Begin 方法来启动异步操作,并传入操作完成时将调用的委托。 可以从回调调用 End 方法,该方法返回异步操作的结果。 或者,可以轮询操作是否已完成或者同步等待该操作,而不是提供回调。
以 Dns.GetHostAddresses 方法为例,该方法接受一个主机名并返回该主机名解析后的 IP 地址数组。 该方法同步版本的签名如下所示: