【问题标题】:Scala parallel file downloadScala 并行文件下载
【发布时间】:2017-02-28 17:58:58
【问题描述】:

假设我有一个 URL 列表和一个下载相应文件的函数:

val urls = List(url1, url2, url3)
def fetch(url: String): File = ...

我想知道是否有比这更好的并行下载这些文件的方法:

val futureFiles: Future[List[File]] = Future {
  urls.par.map(fetch)
}
futureFiles.map(files => ...)

我看到的一个问题是,现在我只能在文件全部下载后才能访问它们。如何做到简洁优雅,并且能够对每个下载的文件进行操作?

【问题讨论】:

  • 顺便说一句,您不能将一些计算包装在Future 中并期望它是异步/非阻塞的(请参阅注释here)。如果它是那么简单,我会多么喜欢...... xD
  • 你最好不要使用Play WS library,或者其他一些真正异步的HTTP库。
  • @insane-e 给定一个正确配置的执行上下文,下载仍然会并行发生,这就是他所要求的,还是我错了?您是对的,您不能通过在将来包装阻塞代码来使其成为非阻塞,但这正是我们目前大多数 SQL 驱动程序的情况。是的,为 Play WS +1 :)
  • @fxlae 是的,它会并行执行。最终结果是一样的。 :) 唯一的区别是(我认为)运行这些 HTTP 请求的线程将被阻塞(等待,不做任何有用的操作)直到返回响应。

标签: scala parallel-processing future


【解决方案1】:

怎么样:

urls.par.map(fetch).map(file => ...)

这样,获取文件的获取和“处理”都是并行完成的。

【讨论】:

    【解决方案2】:

    ...并行下载这些文件的更好方法...

    这取决于“并行下载这些文件”的确切含义。假设您要下载三个文件(基于您的示例:val urls = List(url1, url2, url3))。这可能意味着两件不同的事情:

    • 只是将下载移出当前线程,而不关心下载本身是并行执行还是顺序执行(因此可能 url1 在 url2 之前获取,然后是 url3),
    • 将下载从当前线程移开,同时并行执行下载(同时获取所有 url)。

    如果第一个选项是您想要的,那么 Tzach Zohar 提供的答案是一个好方法。并行集合会将您的 url 放入分区中,并为每个分区分配一个线程。如果您有 3 个元素,您的下载很可能会按顺序完成,因为只有 1 个分区。如果你的List 的 url 更大,那么你也会得到更多的线程,但每个分区内的 url 仍然会按顺序获取。

    如果您想同时下载所有文件(选项 2),那么您需要对并行性进行更多控制。您的Future 方法并没有那么错误,但不是将整个下载过程放在一个Future 中,而是每个网址都需要一个Future

    您的代码可能如下所示:

    val futureFiles: List[Future[File]] = urls.map(u => Future(fetch(u))) // note: no par
    

    请注意,您现在得到的是 List[Future[File]],而不是之前的 Future[List[File]]。随后,您可以单独映射每个Future,而不必等待一个Future 完成(和以前一样)。

    futureFiles.map(_.map(file => ...))
    

    之后,您可以选择使用Future.sequence 将生成的List[Future[T]] 转换为Future[List[T]]

    您必须确保使用正确配置的ExecutionContext,否则,您的部分下载仍可能会按顺序执行。除此之外,用真正异步的东西替换阻塞 IO 是个好主意(请参阅 insan-e 的评论)。

    【讨论】:

      猜你喜欢
      • 2016-02-26
      • 1970-01-01
      • 2017-07-29
      • 1970-01-01
      • 2023-03-26
      • 2013-10-11
      • 1970-01-01
      • 1970-01-01
      • 2019-10-09
      相关资源
      最近更新 更多