F#中的全局状态和异步工作流

时间:2013-04-07 01:11:01

标签: f#

用于说明F#中异步工作流的常见示例是并行检索多个网页。下面给出了一个这样的示例:http://en.wikibooks.org/wiki/F_Sharp_Programming/Async_Workflows此处显示的代码,以防链接在未来发生变化:

open System.Text.RegularExpressions
open System.Net

let download url =
    let webclient = new System.Net.WebClient()
    webclient.DownloadString(url : string)

let extractLinks html = Regex.Matches(html, @"http://\S+")

let downloadAndExtractLinks url =
    let links = (url |> download |> extractLinks)
    url, links.Count

let urls =
     [@"http://www.craigslist.com/";
     @"http://www.msn.com/";
     @"http://en.wikibooks.org/wiki/Main_Page";
     @"http://www.wordpress.com/";
     @"http://news.google.com/";]

let pmap f l =
    seq { for a in l -> async { return f a } }
    |> Async.Parallel
    |> Async.Run

let testSynchronous() = List.map downloadAndExtractLinks urls
let testAsynchronous() = pmap downloadAndExtractLinks urls

let time msg f =
    let stopwatch = System.Diagnostics.Stopwatch.StartNew()
    let temp = f()
    stopwatch.Stop()
    printfn "(%f ms) %s: %A" stopwatch.Elapsed.TotalMilliseconds msg temp

let main() =
    printfn "Start..."
    time "Synchronous" testSynchronous
    time "Asynchronous" testAsynchronous
    printfn "Done."

main()

我想知道的是如何处理全局状态的变化,例如丢失网络连接?是否有一种优雅的方式来做到这一点?

可以在进行Async.Parallel调用之前检查网络状态,但状态可能会在执行期间发生变化。假设一个人想要做的是暂停执行,直到网络再次可用而不是失败,是否有一种功能性的方法可以做到这一点?

1 个答案:

答案 0 :(得分:5)

首先,该示例存在一个问题 - 它使用Async.Parallel parallel 中运行多个操作,但操作本身并未实现为异步,因此无法避免阻塞线程池中过多的线程。

异步。要使代码完全异步,downloaddownloadAndExtractLinks函数也应该是异步的,这样您就可以使用AsyncDownloadString { {1}}:

WebClient

正在重试。现在,要回答这个问题 - 没有内置机制来处理网络故障等错误,因此您需要自己实现此逻辑。什么是正确的方法取决于您的情况。一种常见的方法是重试操作一定次数并仅在例外情况下抛出异常,例如10倍。您可以将其编写为带有其他异步工作流的原语:

let asyncDownload url = async {
    let webclient = new System.Net.WebClient()
    return! webclient.AsyncDownloadString(System.Uri(url : string)) }

let asyncDownloadAndExtractLinks url = async {
    let! html = asyncDownload url
    let links = extractLinks html
    return url, links.Count }

let pmap f l =
    seq { for a in l -> async { return! f a } }
    |> Async.Parallel
    |> Async.RunSynchronously

然后,您可以更改主要功能以构建重试下载10次的工作流程:

let rec asyncRetry times op = async {
  try
    return! op
  with e ->
    if times <= 1 then return (reraise e)
    else return! asyncRetry (times - 1) op }

共享状态。另一个问题是let testAsynchronous() = pmap (asyncRetry 10 downloadAndExtractLinks) urls 只会在所有下载完成后返回(如果有一个有缺陷的网站,则必须等待)。如果你想在回来时显示结果,你需要更复杂的东西。

执行此操作的一个好方法是使用F#代理 - 创建一个代理,该代理存储到目前为止获得的结果,并且可以处理两条消息 - 一条添加新结果,另一条返回当前状态。然后,您可以启动多个异步任务,将结果发送给代理,并且在单独的异步工作流中,您可以使用轮询来检查当前状态(例如,更新用户界面)。

我为developerFusion写了一个MSDN series about agentstwo articles,其中包含大量带有F#代理的代码示例。