Java从多个调用中收集CompletableFuture的结果

时间:2017-10-30 22:31:55

标签: java multithreading completable-future

我必须运行多个外部调用操作,然后以列表的形式获取结果。 我决定使用CompletableFuture api,我准备的代码非常恶心:

示例:

public class Main {
    public static void main(String[] args) {
        String prefix = "collection_";

        List<CompletableFuture<User>> usersResult = IntStream.range(1, 10)
                .boxed()
                .map(num -> prefix.concat("" + num))
                .map(name -> CompletableFuture.supplyAsync(
                        () -> callApi(name)))
                .collect(Collectors.toList());

        try {
            CompletableFuture.allOf(usersResult.toArray(new CompletableFuture[usersResult.size()])).get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        List<User> users = usersResult //the result I need
                .stream()
                .map(userCompletableFuture -> {
                    try {
                        return userCompletableFuture.get();
                    } catch (InterruptedException | ExecutionException e) {
                        e.printStackTrace();
                    }
                    return null;
                })
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    private static User callApi(String collection) {
        return new User(); //potentially time-consuming operation
    }
}

我有以下问题:

  1. 我可以以某种方式避免重复流中的try-catch块,我将CompletableFuture映射到用户?
  2. 此代码可以减少顺序(我怎样才能避免等待所有期货完成?)
  3. 这样做是否正常(所有未来都会在流中解决?):

    public class Main {
        public static void main(String[] args) {
            String prefix = "collection_";
    
            List<User> usersResult = IntStream.range(1, 10)
                    .boxed()
                    .map(num -> prefix.concat("" + num))
                    .map(name -> CompletableFuture.supplyAsync(
                            () -> callApi(name)))
                    .filter(Objects::nonNull)
                    .map(userCompletableFuture -> {
                        try {
                            return userCompletableFuture.get();
                        } catch (InterruptedException | ExecutionException e) {
                            e.printStackTrace();
                        }
                        return null;
                    })
                    .collect(Collectors.toList());
        }
    
        private static User callApi(String collection) {
            return new User(); //potentially time-consuming operation
        }
    }
    

2 个答案:

答案 0 :(得分:1)

对于1.,您可以完全跳过allOf().get()来电,因为您无论如何都要一个接一个地等待所有未来.¹

对于2.,您可以通过执行以下操作来简化try-catch

  • 使用exceptionally()直接处理异常;
  • 使用join()代替get()以避免检查异常(并且您知道没有例外)。

对于3.,由于您至少需要步骤,因此无法真正减少顺序:创建所有未来然后处理结果。

如果您在单个流中执行所有操作,它将创建每个未来,然后在创建下一个之前立即等待它 - 因此您将失去并行性。您可以使用并行流,但使用CompletableFuture时没有多大好处。

所以最终的代码是:

List<CompletableFuture<User>> usersResult = IntStream.range(1, 10)
        .boxed()
        .map(num -> prefix.concat("" + num))
        .map(name -> CompletableFuture.supplyAsync(() -> callApi(name))
            .exceptionally(e -> {
                e.printStackTrace();
                return null;
            }))
        .collect(Collectors.toList());

List<User> users = usersResult
        .stream()
        .map(CompletableFuture::join)
        .filter(Objects::nonNull)
        .collect(Collectors.toList());

¹请注意,如果您希望结果也是allOf(),则仍需要CompletableFuture<List<User>>来电,例如

final CompletableFuture<List<User>> result =
        CompletableFuture.allOf(usersResult.stream().toArray(CompletableFuture[]::new))
                .thenApply(__ -> usersResult
                        .stream()
                        .map(CompletableFuture::join)
                        .filter(Objects::nonNull)
                        .collect(Collectors.toList()));

答案 1 :(得分:0)

或者,你可以放弃CompletableFuture并使用parallelStream()作为Didier提到的:

Optional<User> wrapApiCall(String name) {
    try { return Optional.of(callApi(name)); }
    catch (Exception e) { 
        e.printStackTrace();
        return Optional.empty(); 
    }
}

List<User> usersResult = IntStream.range(1, 10)
    .boxed()
    .parallelStream()
    .map(num -> String.format("%s%d", prefix, num))
    .map(this::wrapApiCall)
    .filter(Optional::isPresent)
    .map(Optional::get)
    .collect(Collectors.toList());