【问题标题】:Async.Parallel or Array.Parallel.Map?Async.Parallel 还是 Array.Parallel.Map?
【发布时间】:2019-05-29 04:10:50
【问题描述】:

我正在尝试实现我从 Don Syme 的博客中读到的模式

(https://blogs.msdn.microsoft.com/dsyme/2010/01/09/async-and-parallel-design-patterns-in-f-parallelizing-cpu-and-io-computations/)

这表明利用异步 I/O 可以大幅提升性能。我目前正在尝试使用 Array.Parallel.Map 以一种方式“工作”的代码,看看我是否可以使用 Async.Parallel 以某种方式实现相同的结果,但我真的不明白 Async.Parallel,并且无法正常工作。

我有一段代码(为了说明这一点而在下面进行了简化)成功地检索了一个 cusip 的一组数据。 (例如价格序列)

let getStockData cusip = 
    let D = DataProvider()
    let arr = D.GetPriceSeries(cusip)
    return arr

let data = Array.Parallel.map (fun x -> getStockData x) stockCusips

因此,这种方法构建了一个数组数组,通过互联网与我的每只股票(可能多达 3000 个)的数据供应商建立连接,并返回给我一个数组数组(每只股票 1 个,带有价格每个系列)。我诚然不明白 Array.Parallel.map 下发生了什么,但我想知道这是否是一种在后台浪费资源的场景,而实际上使用异步 I/O 可能会更快?因此,为了测试这一点,我尝试使用 asyncs 来制作这个函数,我认为下面的函数遵循 Don Syme 文章中使用 URL 的模式,但它不会使用“let!”进行编译。

let getStockDataAsync cusip = 
    async {  let D = DataProvider()
             let! arr = D.GetData(cusip)
             return arr
          } 

我得到的错误是: 此表达式应具有类型 Async 但此处具有类型 obj

使用“let”而不是“let!”可以很好地编译,但我认为重点是您需要感叹号才能使命令在不阻塞线程的情况下运行。

所以第一个问题确实是,我上面的语法有什么问题,在 getStockDataAsync 中,然后在更高的层次上,任何人都可以提供一些关于异步 I/O 的额外见解,以及我提出的场景是否会从中受益,使其可能比 Array.Parallel.map 快得多?非常感谢。

【问题讨论】:

    标签: f#


    【解决方案1】:

    F# 异步工作流允许您实现异步计算,但是,F# 区分了普通计算和异步计算。这种差异由类型系统跟踪。例如,一个下载网页并且是同步的方法有一个类型string -> string(获取URL并返回HTML),但是一个异步执行同样事情的方法有一个类型string -> Async<string>。在async 块中,您可以使用let! 调用异步操作,但所有其他(标准同步)方法必须使用let 调用。现在,您的示例的问题是GetData 操作是普通同步方法,因此您不能使用let! 调用它。

    在典型的 F# 场景中,如果要使 GetData 成员异步,则需要使用异步工作流来实现它,因此还需要将其包装在 async 块中。在某些时候,您将到达一个您真正需要异步运行一些原始操作的位置(例如,从网站下载数据)。 F# 提供了几个原始异步操作,您可以使用 let!async 块调用这些操作,例如 AsyncGetResponse(这是 GetResponse 方法的异步版本)。因此,在您的 GetData 方法中,您将例如编写如下内容:

    let GetData (url:string) = async {
      let req = WebRequest.Create(url)
      let! rsp = req.AsyncGetResponse()
      use stream = rsp.GetResponseStream()
      use reader = new System.IO.StreamReader(stream)
      let html = reader.AsyncReadToEnd() 
      return CalculateResult(html) }
    

    总结就是你需要识别一些原始的异步操作(比如等待web服务器或者文件系统),在那个时候使用原始的异步操作并将所有使用这些操作的代码包装在async块。如果没有可以异步运行的原始操作,那么您的代码受 CPU 限制,您可以使用 Parallel.map

    我希望这可以帮助您了解 F# 异步工作流的工作原理。如需更多信息,您可以查看Don Syme's blog post,关于asynchronous programming by Robert Pickering 的系列,或我的F# web cast

    【讨论】:

    • 感谢您的详细解释。这很有帮助。
    • @tomas_petricek 您好 Tomas,您的 F# 网络广播的链接已损坏。您有此内容的新位置吗?如果它仍然可用,我有兴趣查看它。
    • 这里是一个更正的链接:tomasp.net/blog/fsharp-webcast-async.aspx Silverlight 播放器不再工作,但第 9 频道的链接可以工作!
    【解决方案2】:

    @Tomas 已经给出了很好的答案。我只是补充几句。

    F# asyncs 的习惯用法是使用“Async”前缀命名方法(AsyncFoo,而不是 FooAsync;后者是另一种 .NET 技术已经使用的习惯用法)。所以你的函数应该是getStockDataasyncGetStockData

    在异步工作流中,每当您使用let! 代替letdo! 代替do 时,右侧的内容应该具有Async<T> 类型而不是T。基本上,您需要一个现有的异步计算才能在工作流程的这一点上“异步”。每个Async<T> 本身要么是其他async{...} 工作流,要么是异步“原始”。原语在 F# 库中定义或通过Async.FromBeginEndAsync.FromContinuations 在用户代码中创建,它们可以定义启动计算、注册 I/O 回调、释放线程和重新启动计算的低级细节当被召回时。因此,为了充分利用异步 I/O 的优势,您必须一直“探查”异步到一些真正的异步 I/O 原语。

    【讨论】:

      猜你喜欢
      • 2016-05-28
      • 1970-01-01
      • 2020-11-14
      • 1970-01-01
      • 2021-08-07
      • 2016-03-26
      • 2014-03-05
      • 2017-05-03
      • 2015-06-10
      相关资源
      最近更新 更多