簡體   English   中英

如何對CompletableFutures流進行重新排序?

[英]How to reorder Stream of CompletableFutures?

我處理CompletableFutures的流。 這些需要不同的時間才能完成。 那些花費較長時間進行塊流處理而其他人可能已經完成的處理(我知道並行流)

因此,我想對流中的項目進行重新排序(例如,使用緩沖區),以使完成的期貨前進。

例如,如果一個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(()-> f.get())嗎?

我該怎么做?

[編輯]

  1. 更改示例以包含filter()
  2. 添加了評論
  3. 添加了CompletionService鏈接

擁有更多上下文肯定會幫助您量身定制答案-我感到問題在其他地方,可以通過更輕松的方式解決。

但是,如果您的問題是如何在開始時以某種方式保留已完成的期貨,則幾乎沒有選擇:


使用自定義Comparator Stream進行排序:

.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())

我假設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);

這很簡單,但並不簡單。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM