使用异步工作流实现并行的最佳实践

8
假设我想要抓取一个网页并提取一些数据。我很可能会写出以下代码:
let getAllHyperlinks(url:string) =
    async {  let req = WebRequest.Create(url)
             let! rsp = req.GetResponseAsync()
             use stream = rsp.GetResponseStream()             // depends on rsp
             use reader = new System.IO.StreamReader(stream)  // depends on stream
             let! data = reader.AsyncReadToEnd()              // depends on reader
             return extractAllUrls(data) }                    // depends on data
let! 告诉 F# 在另一个线程中执行代码,然后将结果绑定到变量中并继续处理。上面的示例使用了两个 let 语句:一个用于获取响应,另一个用于读取所有数据,因此它会产生至少两个线程(如果我错了,请纠正我)。
尽管上述工作流会生成多个线程,但由于工作流中的每个项都依赖于前一个项,因此执行顺序是串行的。在其他线程返回之前,不可能评估工作流中的任何更深层次的项。
在上面的代码中,有多个 let! 是否有任何好处?
如果没有,那么如何更改此代码以利用多个 let! 语句?
2个回答

10
关键在于我们没有创建任何新的线程。整个工作流程中,从线程池中消耗的活动线程数为1或0。(一个例外是,在第一个'!'之前,代码在执行 Async.Run 操作的用户线程上运行。)"let!" 在异步操作游离时释放线程,然后在操作返回时从线程池中获取线程。(性能)优点是减少对线程池的压力(当然,主要的用户优点是简单的编程模型——比你写的所有 BeginFoo/EndFoo/callback 都好一百万倍)。
另请参见:http://cs.hubfs.net/forums/thread/8262.aspx

好的,所以 let! 并不会生成多个线程,它只是将线程句柄释放回线程池 :) 我想这会带来一些开销,因此我可能不会在每一行上都使用"let!"。是否有任何规则可以将 "let!" 放置在最优位置? - Juliet
在每个需要进行异步调用的行上加上let!,这些调用会花费一些时间,并且在等待期间不需要线程(例如从网络或文件流中读取)。因此,在您的示例中,两个“let!”都是“好的”。 - Brian
如果您要运行许多工作流的副本,任何“let!”的开销都将被通过使CPU保持活动状态而无需生成额外线程来获得的回报所抵消。 - Brian
谢谢,Brian,你的回复很有帮助,我很感激 :) - Juliet

3

我正在写答案,但是Brian比我更快。我完全同意他的观点。

我想补充一下,如果你想并行处理同步代码,正确的工具是PLINQ,而不是异步工作流,如Don Syme 解释所示。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接