如何重新排序CompletableFutures的流?

时间:2017-08-21 07:26:19

标签: java java-stream completable-future

我处理CompletableFutures的Streams。这些需要不同的时间来完成。那些需要更长时间的块流处理,而其他人可能已经完成(我知道并行流)

因此,我想重新排序Stream中的项目(例如使用缓冲区),以便提前完成未完成的期货。

例如,如果一个getUser调用需要很长时间,则此代码会阻止流处理

public static Boolean isValid(User user) { ... }

emails.stream()
   // not using ::
   // getUser() returns CompletableFuture<User>
  .map( e -> getUser(e))
  // this line blocks Stream processing
  .filter( userF -> isValid( userF.get()) )
  .map( f -> f.thenApply(User::getName))

我想要像

这样的东西
emails.stream()
   .map( e -> getUser(e))
   // this moves Futures into a bounded buffer
   // and puts those finished first
   // like CompletionService [1]
   // and returns a Stream again
   .collect(FutureReorderer.collector())
   // this is not the same Stream but
   // the one created by FutureReorderer.collector()
   .filter( userF -> isValid( userF.get()) )
   .map( f -> f.thenApply(User::getName))

[1]例如,CompletionService https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorCompletionService.html在调用take()时返回已完成的任务,否则返回阻塞。但是CompletionService不接受期货,是否需要做cs.sumbit(() - &gt; f.get())?

我该怎么做?

[编辑]

  1. 更改了示例以包含filter()
  2. 添加评论
  3. 添加了CompletionService链接

2 个答案:

答案 0 :(得分:1)

拥有更多上下文肯定有助于定制答案 - 我感觉问题在其他地方并且可以更容易地解决。

但如果你的问题是如何在某种程度上保留完成的期货,那么几乎没有选择:

使用自定义StreamComparator进行排序:

.sorted(Comparator.comparing(f -> !f.isDone()))

请记住,isDone不仅在未来成功完成时返回true。

PriorityQueue

存储期货
PriorityQueue<CompletableFuture<String>> queue
 = new PriorityQueue<>(Comparator.comparing(f -> !f.isDone()));

轮询元素时,队列将根据提供的顺序返回元素。

这是在行动:

PriorityQueue<CompletableFuture<String>> queue
 = new PriorityQueue<>(Comparator.comparing(f -> !f.isDone()));

queue.add(CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(Integer.MAX_VALUE);
    } catch (InterruptedException e) {  }

    return "42";
}));

queue.add(CompletableFuture.completedFuture("completed"));

queue.poll(); // "completed"
queue.poll(); // still going on

重要的是要记住,如果您确实希望将PriorityQueue转换为Stream,则无法使用stream()执行此操作 - 这不会保留优先顺序。

这是正确的方法:

Stream.generate(queue::poll).limit(queue.size())

答案 1 :(得分:-1)

我假设OP中的要求同时执行getUser并按完成顺序处理结果Futures。以下是ExecutorCompletionService的解决方案:

final CompletionService<User> ecs = new ExecutorCompletionService<>(executor);

emails.stream().map(e -> ecs.submit(() -> getUser(e).get()))
    .collect(Collectors.collectingAndThen(Collectors.toList(), fs -> fs.stream())) // collect the future list for concurrent execution
    .map(f -> {
            try {
                return ecs.take().get();
            } catch (InterruptedException | ExecutionException e) {
                throw new RuntimeException(e);
            }
        })
    .filter(u -> isValid(u)).map(User::getName)... //TODO;

或者:

final BlockingQueue<Future<User>> queue = new ArrayBlockingQueue<>(emails.size());
final CompletionService<User> ecs = new ExecutorCompletionService<>(executor, queue);        
emails.stream().forEach(e -> ecs.submit(() -> getUser(e).get()));

IntStream.range(0, emails.size())
    .mapToObj(i -> {
            try {
                return queue.poll().get();
            } catch (InterruptedException | ExecutionException e) {
                throw new RuntimeException(e);
            }
        })
    .filter(u -> isValid(u)).map(User::getName);

这很简单,但并不简单。