为什么要在异步中嵌入异步?
Why embed async in async?
我从 Expert f# 书中阅读了以下代码,
为什么函数 collectLinks
将 let! html = async { .... }
嵌入到外部 async
块中?通过删除内部异步来平整它怎么样?
urlCollector
中函数 waitForUrl
的相同问题,它在外部 async
块中有一个 do! Async.StartChild (async {....}) |> Async.Ignore
。扁平化怎么样?
这个实现和用block queue实现的相比怎么样? https://msdn.microsoft.com/en-us/library/vstudio/hh297096(v=vs.100).aspx 创建一个包含 5 的块队列,并将 link 加入生产者队列。
代码:
open System.Collections.Generic
open System.Net
open System.IO
open System.Threading
open System.Text.RegularExpressions
let limit = 50
let linkPat = "href=\s*\"[^\"h]*(http://[^&\"]*)\""
let getLinks (txt:string) =
[ for m in Regex.Matches(txt,linkPat) -> m.Groups.Item(1).Value ]
// A type that helps limit the number of active web requests
type RequestGate(n:int) =
let semaphore = new Semaphore(initialCount=n, maximumCount=n)
member x.AsyncAcquire(?timeout) =
async { let! ok = Async.AwaitWaitHandle(semaphore,
?millisecondsTimeout=timeout)
if ok then
return
{ new System.IDisposable with
member x.Dispose() =
semaphore.Release() |> ignore }
else
return! failwith "couldn't acquire a semaphore" }
// Gate the number of active web requests
let webRequestGate = RequestGate(5)
// Fetch the URL, and post the results to the urlCollector.
let collectLinks (url:string) =
async { // An Async web request with a global gate
let! html =
async { // Acquire an entry in the webRequestGate. Release
// it when 'holder' goes out of scope
use! holder = webRequestGate.AsyncAcquire()
let req = WebRequest.Create(url,Timeout=5)
// Wait for the WebResponse
use! response = req.AsyncGetResponse()
// Get the response stream
use reader = new StreamReader(response.GetResponseStream())
// Read the response stream (note: a synchronous read)
return reader.ReadToEnd() }
// Compute the links, synchronously
let links = getLinks html
// Report, synchronously
do printfn "finished reading %s, got %d links" url (List.length links)
// We're done
return links }
/// 'urlCollector' is a single agent that receives URLs as messages. It creates new
/// asynchronous tasks that post messages back to this object.
let urlCollector =
MailboxProcessor.Start(fun self ->
// This is the main state of the urlCollector
let rec waitForUrl (visited : Set<string>) =
async { // Check the limit
if visited.Count < limit then
// Wait for a URL...
let! url = self.Receive()
if not (visited.Contains(url)) then
// Start off a new task for the new url. Each collects
// links and posts them back to the urlCollector.
do! Async.StartChild
(async { let! links = collectLinks url
for link in links do
self.Post link }) |> Async.Ignore
// Recurse into the waiting state
return! waitForUrl(visited.Add(url)) }
// This is the initial state.
waitForUrl(Set.empty))
我能想到为什么 async
代码会调用另一个异步块的一个原因,那就是它可以让您更早地处理资源 - 当嵌套块完成时。为了演示这一点,这里有一个小助手,它在调用 Dispose
时打印一条消息:
let printOnDispose text =
{ new System.IDisposable with
member x.Dispose() = printfn "%s" text }
下面使用nested async
在嵌套块中做一些事情,然后清理嵌套块中使用的本地资源。然后它再睡一会儿并清理外部块中使用的资源:
async {
use bye = printOnDispose "bye from outer block"
let! r = async {
use bye = printOnDispose "bye from nested block"
do! Async.Sleep(1000)
return 1 }
do! Async.Sleep(1000) }
|> Async.Start
这里"nested block"资源1秒后处理,外块资源2秒后处理
在其他情况下嵌套 async
很有用(例如从包含 try
.. with
的异步块返回),但我认为这不适用于此处。
我从 Expert f# 书中阅读了以下代码,
为什么函数
collectLinks
将let! html = async { .... }
嵌入到外部async
块中?通过删除内部异步来平整它怎么样?urlCollector
中函数waitForUrl
的相同问题,它在外部async
块中有一个do! Async.StartChild (async {....}) |> Async.Ignore
。扁平化怎么样?这个实现和用block queue实现的相比怎么样? https://msdn.microsoft.com/en-us/library/vstudio/hh297096(v=vs.100).aspx 创建一个包含 5 的块队列,并将 link 加入生产者队列。
代码:
open System.Collections.Generic
open System.Net
open System.IO
open System.Threading
open System.Text.RegularExpressions
let limit = 50
let linkPat = "href=\s*\"[^\"h]*(http://[^&\"]*)\""
let getLinks (txt:string) =
[ for m in Regex.Matches(txt,linkPat) -> m.Groups.Item(1).Value ]
// A type that helps limit the number of active web requests
type RequestGate(n:int) =
let semaphore = new Semaphore(initialCount=n, maximumCount=n)
member x.AsyncAcquire(?timeout) =
async { let! ok = Async.AwaitWaitHandle(semaphore,
?millisecondsTimeout=timeout)
if ok then
return
{ new System.IDisposable with
member x.Dispose() =
semaphore.Release() |> ignore }
else
return! failwith "couldn't acquire a semaphore" }
// Gate the number of active web requests
let webRequestGate = RequestGate(5)
// Fetch the URL, and post the results to the urlCollector.
let collectLinks (url:string) =
async { // An Async web request with a global gate
let! html =
async { // Acquire an entry in the webRequestGate. Release
// it when 'holder' goes out of scope
use! holder = webRequestGate.AsyncAcquire()
let req = WebRequest.Create(url,Timeout=5)
// Wait for the WebResponse
use! response = req.AsyncGetResponse()
// Get the response stream
use reader = new StreamReader(response.GetResponseStream())
// Read the response stream (note: a synchronous read)
return reader.ReadToEnd() }
// Compute the links, synchronously
let links = getLinks html
// Report, synchronously
do printfn "finished reading %s, got %d links" url (List.length links)
// We're done
return links }
/// 'urlCollector' is a single agent that receives URLs as messages. It creates new
/// asynchronous tasks that post messages back to this object.
let urlCollector =
MailboxProcessor.Start(fun self ->
// This is the main state of the urlCollector
let rec waitForUrl (visited : Set<string>) =
async { // Check the limit
if visited.Count < limit then
// Wait for a URL...
let! url = self.Receive()
if not (visited.Contains(url)) then
// Start off a new task for the new url. Each collects
// links and posts them back to the urlCollector.
do! Async.StartChild
(async { let! links = collectLinks url
for link in links do
self.Post link }) |> Async.Ignore
// Recurse into the waiting state
return! waitForUrl(visited.Add(url)) }
// This is the initial state.
waitForUrl(Set.empty))
我能想到为什么 async
代码会调用另一个异步块的一个原因,那就是它可以让您更早地处理资源 - 当嵌套块完成时。为了演示这一点,这里有一个小助手,它在调用 Dispose
时打印一条消息:
let printOnDispose text =
{ new System.IDisposable with
member x.Dispose() = printfn "%s" text }
下面使用nested async
在嵌套块中做一些事情,然后清理嵌套块中使用的本地资源。然后它再睡一会儿并清理外部块中使用的资源:
async {
use bye = printOnDispose "bye from outer block"
let! r = async {
use bye = printOnDispose "bye from nested block"
do! Async.Sleep(1000)
return 1 }
do! Async.Sleep(1000) }
|> Async.Start
这里"nested block"资源1秒后处理,外块资源2秒后处理
在其他情况下嵌套 async
很有用(例如从包含 try
.. with
的异步块返回),但我认为这不适用于此处。