使用CompletableFuture.supplyAsync返回多个值

时间:2017-01-09 02:09:49

标签: java java-8 java-stream completable-future

我正在编写一个程序来从源代码下载历史报价。源提供每天需要解析和处理的文件。该程序使用CompletableFuture使用不同的阶段并行下载多个文件。第一阶段是使用HttpClient进行Http调用并获得响应。

getHttpResponse()方法返回CloseableHttpResponse对象。我还想返回一个为此http请求发出的URL。最简单的方法是让一个包装器对象具有这两个字段,但我觉得让一个类只包含这两个字段太多了。我可以用CompletableFuture或Streams来实现这个目标吗?

  filesToDownload.stream()
                 .map(url -> CompletableFuture.supplyAsync(() -> this.getHttpResponse(url), this.executor) )
                 .map(httpResponseFuture -> httpResponseFuture.thenAccept(t -> processHttpResponse(t)))
                 .count();

1 个答案:

答案 0 :(得分:1)

目前尚不清楚为什么要不惜一切代价引入Stream API。将CompletableFuture用法拆分为两个map操作会导致问题,否则将无法解决。除此之外,使用map副作用是滥用Stream API。如果filesToDownload是具有已知大小的Stream源(几乎每个Collection),这可能在Java 9中完全破坏。然后,count()将返回已知大小,而不处理map操作的功能......

如果您想将URLCloseableHttpResponse传递给processHttpResponse,您可以轻松完成:

filesToDownload.forEach(url ->
    CompletableFuture.supplyAsync(() -> this.getHttpResponse(url), this.executor)
                     .thenAccept(  t -> processHttpResponse(t, url))
);

即便如果您使用Stream API收集结果,也没有理由将CompletableFuture拆分为多个map操作:

List<…> result = filesToDownload.stream()
  .map(url -> CompletableFuture.supplyAsync(() -> this.getHttpResponse(url), this.executor)
                               .thenApply(   t -> processHttpResponse(t, url))  )
  .collect(Collectors.toList())
  .stream()
  .map(CompletableFuture::join)
  .collect(Collectors.toList());

请注意,在等待第二个Stream操作中的任何结果之前,这会将CompletableFuture收集到List中。这比使用并行Stream操作更可取,因为它确保在开始等待之前已经提交了所有异步操作。

使用单个Stream管道意味着在提交第二个作业之前等待完成第一个作业,并且使用并行Stream只会减少该问题而不是解决问题。它将取决于Stream实现的执行策略(默认的Fork / Join池),它会干扰指定执行程序的实际策略。例如,如果指定的执行程序应该使用比CPU核心更多的线程,则Stream仍然会一次提交与核心一样多的作业 - 如果默认的Fork / Join池中有其他作业,则更少。 / p>

相反,上述解决方案的行为将完全由指定执行程序的执行策略控制。