等待List<Future>中的每个Future都完成

7

我需要调用一个方法来返回一个Future,对于List<Principal>中的每个元素,我都需要调用一次该方法,因此最终会得到一个List<Future<UserRecord>>

返回Future的方法是库代码,我无法控制该代码如何运行,我只有Future这个对象。

在进一步操作之前,我希望等待所有Future完成(成功或失败)。

是否有更好的方法来实现这一点,而不是使用以下方法:

List<Principal> users = new ArrayList<>();
// Fill users
List<Future<UserRecord>> futures = getAllTheFutures(users);
List<UserRecord> results = new ArrayList<>(futures.size());
boolean[] taskCompleted = new boolean[futures.size()];
for (int j = 0; j < taskCompleted.length; j++) {
    taskCompleted[j] = false;
}
do {
    for (int i = 0; i < futures.size(); i++) {
        if (!taskCompleted[i]) {
            try {
                results.add(i, futures.get(i).get(20, TimeUnit.MILLISECONDS));
                taskCompleted[i] = true;
            } catch (TimeoutException e) {
                // Do nothing
            } catch (InterruptedException | ExecutionException e) {
                // Handle appropriately, then...
                taskCompleted[i] = true;
            }
        }
    }
} while (allNotCompleted(taskCompleted));

如果您有兴趣:

private boolean allNotCompleted(boolean[] completed) {
    for (boolean b : completed) {
        if (!b)
            return true;
    }
    return false;
}

等待一个 Future 列表的答案不同,我无法控制创建 Future 的代码。


1
allNotCompleted(taskCompleted) 这段代码片段会检查整个数组并返回一个总体结果吗? - Sachith Dickwella
是的,当然可以。 - markvgti
2
为什么这么复杂?for (Future<UserRecord> future : futures) results.add(future.get());有什么问题吗? - shmosel
1
@shmosel 可能是因为我过于深思熟虑了 :-). - markvgti
@Elysiumplain 返回 Future 的方法是外部的,不需要也无用添加你自己的执行器。 - Kayaman
显示剩余2条评论
2个回答

5

你的代码可以被大大简化。除非你在问题中没有说明要求,否则可以编写等效版本如下。

List<Principal> users = // fill users
List<Future<UserRecord>> futures = getAllTheFutures(users);
List<UserRecord> results = new ArrayList<>();

for (int i = 0; i < futures.size(); i++) {
        try {
            results.add(futures.get(i).get());
        } catch (InterruptedException | ExecutionException e) {
            // Handle appropriately, results.add(null) or just leave it out
        }
    }
}

所以你唯一删掉的是get函数里面的等待,这很有道理。你忽略的一个问题是第i个future的结果会被添加到results数组的第i个索引上。所有那些挖苦的话都是不必要的。但或许这只是你获取乐趣的方式吧。 - markvgti
@markvgti 我真的不明白你为什么关心索引。也许你想保留一个单独的 List<Principal> failed,在其中添加未来抛出异常的原则,使用 failed.add(users.get(i))。我试图提供好的答案,但当有人提供错误的答案(并且人们因为不理解它们是错误的而投票支持这些错误的答案)时,这很难做到。 - Kayaman
事物按顺序生成,将每个东西在链条中保持有序只是更容易的。感谢您简单的代码和指出等待是不必要的。我能够通过查看代码理解好的和坏的。如果我对自己的解决方案有信心,我就不会在这里寻求帮助了。 - markvgti
3
“for”循环按顺序迭代元素,“add”将在列表的当前结尾附加元素,因此结果列表与源列表的顺序相同。实际上,即使是这个答案的代码也比必要的复杂。甚至可以使用更简单的“for (Future<UserRecord> future : futures) try { results.add(future); } catch (…) { …}”代码。无需处理索引。 - Holger

3
你可以简单地做一个缩减列表; 从你的列表中删除成功的响应并迭代直到为空。
List<Principal> users = // fill users
List<Future<UserRecord>> futures = getAllTheFutures(users);
List<UserRecord> results = new ArrayList<>();

for (int i = 0; i < futures.size(); i++) {
        try {
            results.add(futures.get(i).get(<how long you want before your application throws exception>));

        }
        catch (InterruptedException | ExecutionException e) {
            // Handle appropriately, results.add(null) or just leave it out
        }
        catch (TimeoutException timeoutEx) {
            // If the Future retrieval timed out you can handle here
        }

    }
}

如果您打算在继续之前收集一组工作,那么在此情况下等待返回线程索引X将导致时间成本(大约)为最后返回的线程。

或者,如果您计划在任何一个线程失败时中止该组中的所有线程,则可以使用Java 8 CompletableFuture。

CompletableFuture[] cfs = futures.toArray(new CompletableFuture[futures.size()]);

    return CompletableFuture.allOf(cfs)
            .thenApply(() -> futures.stream()
                                    .map(CompletableFuture::join)
                                    .collect(Collectors.toList())
            );
  • 感谢Kayaman简化了代码库。

不,意图是收集所有的。只是在问题中以一种奇怪和令人困惑的方式实现了。 - Kayaman
你的代码与我的等价,除了你的代码没有按照题目要求去做,即“等待 所有的 Future*。朋友们,这并不难。 - Kayaman
所以我们都同意这个问题基本上没有任何意义,因为Future的文档已经解释了如何实现,并且已经实现了相应的函数。除了显而易见的简化海报示例代码之外,也许应该将此问题视为主观问题并关闭它? - Elysiumplain
我在这里没有看到任何意见问题。问题要求“更好的方法”,而我用更好、惯用的方式回答了。另一个答案只是可怕的,而你的答案一开始是错误的,但现在你加入了CompletableFuture,却没有真正的原因。 - Kayaman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接