想象一下,我有一个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 => ...)
我看到的一个问题是,现在我只能在下载文件后才能访问这些文件。如何实现简洁优雅的东西,并能够在我们去的时候对每个下载的文件采取行动?
答案 0 :(得分:2)
怎么样:
urls.par.map(fetch).map(file => ...)
这样,获取文件的提取和“处理”都是并行完成的。
答案 1 :(得分:0)
...更好的并行下载这些文件的方法......
这取决于你的意思是“并行下载这些文件”。想象一下,您想要下载三个文件(基于您的示例:val urls = List(url1, url2, url3)
)。这可能意味着两件事:
如果第一个选项是您想要的,那么Tzach Zohar提供的答案是一个很好的方法。并行集合将把您的URL放在分区中,并为每个分区分配一个线程。如果你有3个元素,你的下载很可能会顺序完成,因为只有1个分区。如果您的List
网址较大,那么您还会获得更多线程,但每个分区中的网址仍将按顺序获取。
如果您想要同时下载所有文件(选项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 => ...))
您可以选择使用List[Future[T]]
将结果Future[List[T]]
转换为Future.sequence
。
您必须确保使用正确配置的ExecutionContext
,否则,部分下载内容仍可按顺序执行。除此之外,用真正异步的东西替换阻塞IO是个好主意(参见insan-e的评论)。