[英]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] [编辑]
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.