异步使用 HTTP
到现在,我们所关注的还只是通过 HTTP 一次检索一个文档,或进行一次更新,在这种情况下,使用异步编程模式就没什么意义了。然而,我们通常的想法是一次能进行多个 HTTP 请求,这样,就可以从多个源检索、汇总数据,在这种情况下,使用 F# 的异步工作流,我们第一次碰到是在第十章“异步编程”一节,就能极大提高应用程序的性能。事实上,我们可能还希望能得到比使用本地磁盘更高的性能,正如我们在第十章所看到的那样,网络的输入输出要比本地磁盘慢很多,这是因为异步编程模式能够在等待输入输出时不阻塞线程,这样,这个线程就可以继续做其他工作;本质上,它是能够以并行的方式等待多个输入输出完成的。由于可以并行化等待时间,就能够期望从高延迟服务(high latency services)中得到最大限度的速度提高。
假设我们想从流行的社交网,推特(twitter)上检查出所有的朋友,再检查出朋友的所有朋友,我们想用这个网络进行一些分析,尝试在朋友的朋友中间找出我们想要找的人。看看清单11-6 我们是如何做的。
清单11-6异步使用 HTTP
open System
open System.IO
open System.Net
open System.Text
open System.Xml
open System.Xml.XPath
open System.Globalization
open Microsoft.FSharp.Control.WebExtensions
// a record to hold details about a tweeter
type Tweeter =
{Id: int;
Name:string;
ScreenName:string;
PictureUrl:string; }
// turn the xml stream into a stronglytyped list of tweeters
let treatTweeter name (stream: Stream) =
printfn"Processing: %s" name
letxdoc = new XPathDocument(stream)
letnav = xdoc.CreateNavigator()
letxpath = nav.Compile("users/user")
letiter = nav.Select(xpath)
letitems =
[for x in iter ->
let x = x :?> XPathNavigator
let getValue (nodename: string) =
let node = x.SelectSingleNode(nodename)
node.Value
{ Id = Int32.Parse(getValue "id");
Name = getValue "name";
ScreenName = getValue "screen_name";
PictureUrl = getValue "profile_image_url"; } ]
name,items
// function to make the urls of
let friendsUrl = Printf.sprintf"http://twitter.com/statuses/friends/%s.xml"
// asynchronously get the friends of thetweeter
let getTweetters name =
async{ do printfn "Starting request for: %s" name
let req = WebRequest.Create(friendsUrl name)
use! resp = req.AsyncGetResponse()
use stream = resp.GetResponseStream()
return treatTweeter name stream }
// from a single twitter username get allthe friends of friends of that user
let getAllFriendsOfFriends name =
//run the first user synchronously
letname, friends = Async.RunSynchronously (getTweetters name)
//only take the first 99 users since we‘re only allowed 100
//requests an hour from the twitter servers
letlength = min 99 (Seq.length friends)
//get the screen name of all the twitter friends
letfriendsScreenName =
Seq.takelength (Seq.map (fun { ScreenName = sn } -> sn) friends)
//create asynchronous workflows to get friends of friends
letfriendsOfFriendsWorkflows =
Seq.map(fun sn -> getTweetters sn) friendsScreenName
//run this in parallel
letfof = Async.RunSynchronously (Async.Parallel friendsOfFriendsWorkflows)
//return the friend list and the friend of friend list
friendsScreenName,fof
可以看到,异步的部分与对应的同步部分不太一样,代码需要括在 async { ... } 工作流中,并且,当进行异步调用时,需要使用感叹号(!);除此之外,代码没有变化:
let getTweetters name =
async{ let req = WebRequest.Create(friendsUrl name)
use! resp = req.AsyncGetResponse()
use stream = resp.GetResponseStream()
return treatTweeter name stream }
当我们得到了原始中朋友列表后,还要执行一个工作流,因为我们需要等待这个工作流的结果,所以要同步地执行这个工作流。使用 Async.Run 函数执行工作流并等待其结果:
let name, friends = Async.Run (getTweettersname)
另一个要点是如何并行地调用。这个很简单:获得原始朋友列表后,就可以创建异步工作流列表执行了:
// create asynchronous workflows to getfriends of friends
let friendsOfFriendsWorkflows =
Seq.map(fun sn -> getTweetters sn) friendsScreenName
有了这个列表后,能够使用 Async.Parallel 并行执行工作流,用 Async.Run 得到结果:
// run this in parallel
let fof = Async.Run (Async.ParallelfriendsOfFriendsWorkflows)
并行执行所有这些请求,极大地提高了性能,因为服务器可能并行地处理许多请求,就能够减少等待响应的时间。
原文地址:http://blog.csdn.net/hadstj/article/details/26607961