简体   繁体   English

如何对CompletableFutures流进行重新排序?

[英]How to reorder Stream of CompletableFutures?

I deal with Streams of CompletableFutures. 我处理CompletableFutures的流。 These take different times to complete. 这些需要不同的时间才能完成。 Those taking longer block stream processing while others might already have completed (and I know about Parallel Streams) 那些花费较长时间进行块流处理而其他人可能已经完成的处理(我知道并行流)

Therefore I would like to reorder items in a Stream (eg with a buffer) to move completed Futures ahead. 因此,我想对流中的项目进行重新排序(例如,使用缓冲区),以使完成的期货前进。

For example, this code blocks stream processing if one getUser call takes long 例如,如果一个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))

and I would like to have something like 我想要一些类似的东西

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] For example CompletionService https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorCompletionService.html returns completed tasks when calling take() and blocks otherwise. [1]例如,CompletionService https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorCompletionService.html在调用take()时返回已完成的任务,否则阻止。 But CompletionService does not take futures, would one need to do cs.sumbit( () -> f.get() ) ? 但是CompletionService不接受期货,有人需要做cs.sumbit(()-> f.get())吗?

How would I do that? 我该怎么做?

[Edit] [编辑]

  1. Changed example to include filter() 更改示例以包含filter()
  2. Added comment 添加了评论
  3. Added CompletionService link 添加了CompletionService链接

Having more context would definitely help in tailoring the answer - I have a feeling that problem is somewhere else and can be solved in an easier way. 拥有更多上下文肯定会帮助您量身定制答案-我感到问题在其他地方,可以通过更轻松的方式解决。

But if your question is how to somehow keep completed futures at the beginning, there are few options: 但是,如果您的问题是如何在开始时以某种方式保留已完成的期货,则几乎没有选择:


Sorting the Stream using a custom Comparator : 使用自定义Comparator Stream进行排序:

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

Keep in mind that isDone returns true not only when a future completes successfully. 请记住, isDone不仅会在将来成功完成时返回true。


Storing futures in a PriorityQueue 将期货存储在PriorityQueue

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

when polling elements, the queue will be returning elements according to their provided ordering. 在轮询元素时,队列将根据其提供的顺序返回元素。

Here it is in action: 它在起作用:

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

It's important to remember that if you do want to convert PriorityQueue to Stream , you can't do this simply using stream() - this will not preserve the priority order. 重要的是要记住,如果您确实想将PriorityQueue转换为Stream ,则不能仅使用stream()来做到这一点-这不会保留优先级顺序。

This is the right way to go: 这是正确的方法:

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

I assume the requirements in OP is execute getUser concurrently and process the result Futures by completion order. 我假设OP中的要求是同时执行getUser并按完成顺序处理结果Futures。 Here is solution by ExecutorCompletionService : 这是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;

Or: 要么:

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);

It's simple but not straightforward. 这很简单,但并不简单。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM