13.1.2_异步下载网页

13.1.2 异步下载网页

 

    在我们使用异步工作流来获取网页内容之前,需要引用 FSharp.PowerPack.dll 库,它包含许多 .NET 方法的异步版本。当开发独立的应用程序时,可以使用添加引用命令。在这一章中,我们将使用互动开发模式,创建一个新的 F# 脚本文件,使用 #r 指令(清单  13.1)。

 

Listing 13.1 Writing code using asynchronous workflows (F# Interactive)

 

> #r "FSharp.PowerPack.dll";;

> open System.IO
open System.Net;;

> let downloadUrl(url:string) = async {
     let request = HttpWebRequest.Create(url)
     let! response = request.AsyncGetResponse()
     use response = response
     let stream = response.GetResponseStream()
     use reader = new StreamReader(stream)
     return! reader.AsyncReadToEnd() };;
val downloadUrl : string -&gt; Async<string>

 

    打开所需的所有命名空间后, 我们定义一个函数,它使用异步工作流程实现,用 async 值作为计算生成器。可以轻松地证明,它就是一个普通的值;在 Visual Studio 中,如果在值之后立即键入一个点 (.),智能感知会显示它包含的所有常用的计算生成器的成员,比如,Bind 和 Return,以及几个其他的基元,我们在以后会需要。打印的类型签名显示,计算类型是 Async<string>。后面,我们会详细讨论这个类型。

    清单 13.1 中的代码,在执行由 F# 库所提供的异步操作 AsyncGetResponse 基元时,使用 let! 结构。这个方法的返回类型是 Async<WebResponse>,所以,let! 结构组合了两个异步操作,把实际的 WebResponse 值绑定到符号 response 上。这意味着,一旦异步操作完成后,我们就可以使用这个值。

    在下一行使用到了use 基元,给定对象的一旦超出范围,就被释放。我们已经讨论过,在普通的 F# 程序环境中的 use,和在异步工作流内部的行为是类似的。工作流完成时,它将释放 HTTP 响应。我们使用值隐藏(value hiding)去隐藏原来的 response 符号,并声明一个新的,它将被释放。这是一种常见的模式,所以,F# 提供一种简便的方法,使用 use! 基元来写,它简单地组合了 let! 和 use。现在,我们知道这件事,可以把上面的两行替换成:

 

use! response = request.AsyncGetResponse()

 

    在清单 13.1 的最后一行,我们使用了基元 return!,之前从没见过,它可以运行另一个异步操作(就像使用 let! 基元),只是当操作完成时,返回结果,而不是在赋值给符号时。像 do! 基元一样,这是简单的语法糖(syntactic sugar)。计算生成器不需要实现任何其他的成员 ,编译器就可以把代码看作是这样写的(实际的转换更简单):

 

let! text = reader.AsyncReadToEnd()
return text

 

    现在,我们已经有了创建异步计算的 downloadUrl 函数,还应该确定如何可以用它来下载网页的内容。你可以在清单 13.2 中看到,可以使用 Async 模块中的函数来执行工作流。

 

Listing 13.2 Executing asynchronous computations (F# Interactive)

 

&gt; let downloadTask = downloadUrl("http://www.manning.com");;
val downloadTask : Async<string>

&gt; Async.RunSynchronously(downloadTask);;
val it : string = "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-tr
ansitional.dtd"><html><head> (...)"

&gt; let tasks =
[ downloadUrl("http://www.tomasp.net");
downloadUrl("http://www.manning.com") ];;
val tasks : list<Async&lt;string>&gt;

&gt; let all = Async.Parallel(tasks);;
val all : Async<string[]>

&gt; Async.RunSynchronously(all);;
val it : string[] = [ "..."; "..." ]

 

    使用异步工作流写的代码是被延迟的,这意味着,当我们执行第一行的 downloadUrl 函数,它还不会开始下载网页。返回的值 (Async<string> 类型)表示想要运行的计算,就像函数值表示以后可以执行的代码一样。Async 模块提供了运行工作流方法,表 13.1 描述了其中一部分。

 

表 13.1 在标准 F# 库 Async 模块中,可用的处理异步工作流的基元

Primitive Type of primitive and description
RunSynchronously

Async<'T> �C&gt; 'T

在当前纯种上启动级定的工作流。在这个工作流中使用异步操作时,工作流恢复在线程上的用于调用异步回调。此操作会阻塞调用者线程,并等待这个工作流的结果。

Start

Async<unit> �C&gt; unit

在后台(使用线程池线程) 启动给定的工作流,并立即返回。工作流与随后的调用者代码并行执行。如在签名中提示的,工作流不返回值。

CreateAsTask

Async<'T> -&gt; Task<'T>

这个方法仅在 .NET 4.0 上可用。它把异步工作流打包成一个可用于执行它的 Task<'T> 对象。任务可以用 Start 或 RunSynchronously 方法启动,其行为类似于 Async 基元。要获取该工作流的结果,可以使用Result 属性,如果工作流尚未完成,就阻塞。

Parallel

seq<Async&lt;'a>&gt; -&gt; Async<array&lt;'a>&gt;

取一个异步工作流的集合,并返回一个工作流,它以并行方式执行所有参数值。返回的工作流等待所有操作完成,然后,在一个数组中返回它们的结果。

    在清单 13.2 中,我们最初使用 Async.RunSynchronously,阻塞了调用线程,这对于以交互方式测试工作流,是有用的。在下一步,我们创建工作流值的列表。此时,什么也没有启动。一旦我们有了集合,就可以使用 Async.Parallel 方法构建一个工作流,将并行执行列表中的所有工作流。这里,仍不执行任何原始的工作流。要做到这一点,需要再次使用 Async.RunSynchronously,启动组合的工作流,并等待结果。组合的工作流启动所有工作流,并等待所有都完成。

    代码仍阻塞,等待整体结果,但它有效地运行。它使用 .NET 线程池来平衡运行的线程的最大数目。如果我们创建几百个任务,它不会创建几百个线程,因为,这样做效率不高。而是使用数量较少的线程。当工作流使用 let! 结构, 到达基元的异步操作调用,它在系统中注册一个回调,并释放这个线程。因为 .NET 使用线程池管理线程,完成了工作的线程可以重用,以启动另一个异步工作流。当我们使用异步工作流时,并行运行的任务数量可以明显大于直接使用的线程数。

    在本章中,我们需要交互地获取数据,因此,我们感兴趣的是,并行运行的工作流,而不是开发响应的图形用户界面应用程序。后一类应用程序(也称为有反映的应用程序(reactive applications)) 是重要的,第 16 章将关注这个主题。现在,我们已经看到了使用异步工作流的代码表象,下面,就看看它们是如何实现的。

你可能感兴趣的:(开发,工作流,下载,网页,应用程序)