13.1.2 异步下载网页

来源:互联网 发布:体现中国实力的数据 编辑:程序博客网 时间:2024/06/05 14:37

13.1.2 异步下载网页

 

使用异步工作流抓取网页内容,需要引用 FSharp.PowerPack.dll库,它提供了许多 .NET方法的异步版本。开发独立的应用程序时,可以使用添加引用命令;在这一章,我们将使用交互式开发模式,因此,创建新的 F#脚本文件,使用 #r指令(清单  13.1)。

 

清单13.1使用异步工作流写代码(交互式F#

 

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

 

> open System.IO 

open System.Net;;

 

> let downloadUrl(url:string) = async {         [1]

     let request =HttpWebRequest.Create(url) 

     let! response = request.AsyncGetResponse() [2]

     use response =response                 [3]

     let stream =response.GetResponseStream() 

     use reader = newStreamReader(stream) 

     return!reader.AsyncReadToEnd() };;     [4]

val downloadUrl : string -> Async<string>

 

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

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

在下一行[3],使用到了use基本操作,对象一旦超出作用域,就会被释放。我们已经讨论过,在普通 F#程序中 use的用法,异步工作流中的 use非常类似,当工作流完成时,会立即释放 HTTP响应。我们使用值隐藏(value hiding),声明一个将被释放新值,隐藏原来的 response符号。这是一种常用模式,因此,F#提供了更简洁的语法,use!基本操作,把 let! use组合到一起。那么,上面的两行就可以替换成一行:

 

use! response = request.AsyncGetResponse()

 

清单 13.1的最后一行,我们使用了之前从没见过的基本操作 return![4],它能够运行其他的异步操作(就像使用 let!基本操作一样),只是当操作完成时,会返回结果,而不是绑定到符号。与 do!基本操作一样,它也也是简单的语法糖(syntactic sugar)。计算生成器不必实现任何其他的成员,编译器就可以把代码看作是这样写的(实际的转换更简单):

 

let! text = reader.AsyncReadToEnd() 

return text

 

函数downloadUrl创建了异步计算之后,还应该确定如何用它来下载网页的内容。在清单 13.2中,我们使用了 Async模块中的函数来运行工作流。

 

清单13.2异步计算 (F# Interactive)

 

> let downloadTask = downloadUrl("http://www.manning.com/") ;; [1] <-- 生成异步工作流

val downloadTask : Async<string>

 

> Async.RunSynchronously(downloadTask);;      [2]<--运行工作流,等待结果

val it : string = "<!DOCTYPE html PUBLIC"-//W3C//DTD XHTML 1.0 

Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html><head> (...)"

 

> let tasks = 

[ downloadUrl("http://www/tomasp.net"); 

downloadUrl("http://www.manning.com") ];; 

val tasks : list<Async<string>>

 

> let all = Async.Parallel(tasks);;     [3]<--把几个工作流组合起来

val all : Async<string[]>

 

> Async.RunSynchronously(all);; 

val it : string[] = [ "...";"..." ]

 

使用异步工作流写的代码是自动延迟的,因此,当我们执行第一行的 downloadUrl函数,它并不会开始下载网页[1]。返回值(Async<string>类型)表示期望运行的计算,就像函数值表示延迟运行的代码一样。Async模块提供了运行工作流的方法,表 13.1是其中一部分。

 

13.1在标准 F#库的 Async模块中,处理异步工作流的基本操作

基本操作

基本操作的类型和描述

RunSynchronously

Async<'T> –> 'T

在当前线程中启动给定的工作流。异步操作在工作流中使用时,工作流重新开始线程,用于调用异步回调。此操作会阻塞调用者线程,并等待工作流的结果。

Start

Async<unit> –> unit

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

CreateAsTask

Async<'T> -> Task<'T> 

这个方法仅在 .NET 4.0可用。它把异步工作流包装成可用于执行的 Task<'T>对象。任务可以用 Start RunSynchronously方法启动,其行为类似于 Async基本操作。使用Result属性,可以得到工作流的结果,如果工作流尚未完成,就会阻塞。

Parallel

seq<Async<'a>> -> Async<array<'a>>

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

 

在清单 13.2中,我们最初使用 Async.RunSynchronously[2],阻塞了调用线程,这对于以交互方式测试工作流,非常有用。在下一步,我们创建工作流值的列表,但在这里并不启动。有了集合以后,我们就可以使用 Async.Parallel 方法[3]生成一个工作流,并行执行列表中所有工作流。这仍然不会执行任何原始的工作流;只有再使用 Async.RunSynchronously,才启动组合的工作流,并等待结果。组合的工作流启动所有工作流并等待,直到所有的工作流都完成为止。

在等待最终结果期间,代码会阻塞,但运行的效率高了。它使用 .NET线程池来利用运行线程的最大值。如果我们创建了几百个任务,它并不会创建几百个线程,因为这样做的效率并不高,相反,应该使用少量的线程。当工作流遇到使用 let!结构,进行异步基本操作的调用时,会在系统中注册一个回调,并释放这个线程。因为 .NET使用线程池管理线程,完成任务的线程可以被重用,启动其他的异步工作流。当我们使用异步工作流时,并行运行的任务数可以远远大于直接使用的线程数。

在本章,我们以交互方式获取数据,因此,重点在并行运行工作流,并不关注响应灵敏的图形界面。后一类应用(也称为响应式应用(reactive applications))也很重要,我们将在第十六章讨论。现在,我们已经看到了使用异步工作流的代码,下面就看看它们是如何实现的。

0 0