web-dev-qa-db-fra.com

CompletableFuture in loop: Comment collecter toutes les réponses et gérer les erreurs

J'essaie d'appeler une API de repos pour la demande PUT dans une boucle. Chaque appel est un CompletableFuture. Chaque appel api renvoie un objet de type RoomTypes.RoomType

  • Je souhaite collecter les réponses (à la fois les réponses réussies et les réponses d'erreur) dans différentes listes. Comment puis-je y parvenir? Je suis sûr que je ne peux pas utiliser allOf car il n'obtiendrait pas tous les résultats si un appel ne parvient pas à se mettre à jour.

  • Comment enregistrer les erreurs/exceptions pour chaque appel?


public void sendRequestsAsync(Map<Integer, List> map1) {
    List<CompletableFuture<Void>> completableFutures = new ArrayList<>(); //List to hold all the completable futures
    List<RoomTypes.RoomType> responses = new ArrayList<>(); //List for responses
    ExecutorService yourOwnExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    for (Map.Entry<Integer, List> entry :map1.entrySet()) { 
        CompletableFuture requestCompletableFuture = CompletableFuture
                .supplyAsync(
                        () -> 
            //API call which returns object of type RoomTypes.RoomType
            updateService.updateRoom(51,33,759,entry.getKey(),
                           new RoomTypes.RoomType(entry.getKey(),map2.get(entry.getKey()),
                                    entry.getValue())),
                    yourOwnExecutor
            )//Supply the task you wanna run, in your case http request
            .thenApply(responses::add);

    completableFutures.add(requestCompletableFuture);
}
8
Rudrani Angira

Vous pouvez simplement utiliser allOf() pour obtenir un avenir qui est terminé lorsque tous vos futurs initiaux sont terminés (exceptionnellement ou non), puis les diviser entre succès et échec en utilisant Collectors.partitioningBy() :

List<CompletableFuture<RoomTypes.RoomType>> completableFutures = new ArrayList<>(); //List to hold all the completable futures
ExecutorService yourOwnExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

for (Map.Entry<Integer, List> entry : map1.entrySet()) {
    CompletableFuture<RoomTypes.RoomType> requestCompletableFuture = CompletableFuture
            .supplyAsync(
                    () ->
                //API call which returns object of type RoomTypes.RoomType
                updateService.updateRoom(51, 33, 759, entry.getKey(),
                        new RoomTypes.RoomType(entry.getKey(), map2.get(entry.getKey()),
                                entry.getValue())),
                    yourOwnExecutor
            );

    completableFutures.add(requestCompletableFuture);
}

CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0]))
        // avoid throwing an exception in the join() call
        .exceptionally(ex -> null)
        .join();
Map<Boolean, List<CompletableFuture<RoomTypes.RoomType>>> result =
        completableFutures.stream()
                .collect(Collectors.partitioningBy(CompletableFuture::isCompletedExceptionally)));

La carte résultante contiendra une entrée avec true pour les futures échoués, et une autre entrée avec false clé pour les réussites. Vous pouvez ensuite inspecter les 2 entrées pour agir en conséquence.

Notez qu'il y a 2 légères modifications par rapport à votre code d'origine:

  • requestCompletableFuture est maintenant un CompletableFuture<RoomTypes.RoomType>
  • thenApply(responses::add) et la liste de responses ont été supprimées

Concernant la journalisation/la gestion des exceptions, ajoutez simplement la requestCompletableFuture.handle() appropriée pour les enregistrer individuellement, mais conservez la requestCompletableFuture et non celle résultant de handle().

9
Didier L

Alternativement, vous pouvez peut-être aborder le problème sous un angle différent et au lieu de forcer l'utilisation de CompletableFuture, vous pouvez utiliser un CompletionService à la place.

L'idée générale du CompletionService est que dès qu'une réponse pour un futur donné est prête, elle est placée dans une file d'attente à partir de laquelle vous pouvez consommer les résultats.

Alternative 1: Sans CompletableFuture

CompletionService<String> cs = new ExecutorCompletionService<>(executor);

List<Future<String>> futures = new ArrayList<>();

futures.add(cs.submit(() -> "One"));
futures.add(cs.submit(() -> "Two"));
futures.add(cs.submit(() -> "Three"));
futures.add(cs.submit(() -> { throw new RuntimeException("Sucks to be four"); }));
futures.add(cs.submit(() -> "Five"));


List<String> successes = new ArrayList<>();
List<String> failures = new ArrayList<>();

while (futures.size() > 0) {
    Future<String> f = cs.poll();
    if (f != null) {
        futures.remove(f);
        try {
            //at this point the future is guaranteed to be solved
            //so there won't be any blocking here
            String value = f.get();
            successes.add(value);
        } catch (Exception e) {
            failures.add(e.getMessage());
        }
    }
}

System.out.println(successes); 
System.out.println(failures);

Ce qui donne:

[One, Two, Three, Five]
[Java.lang.RuntimeException: Sucks to be four]

Alternative 2: Avec CompletableFuture

Cependant, si vous avez vraiment, vraiment besoin de gérer CompletableFuture, vous pouvez également les soumettre au service d'achèvement, simplement en les plaçant directement dans sa file d'attente:

Par exemple, la variation suivante a le même résultat:

BlockingQueue<Future<String>> tasks = new ArrayBlockingQueue<>(5);
CompletionService<String> cs = new ExecutorCompletionService<>(executor, tasks);

List<Future<String>> futures = new ArrayList<>();

futures.add(CompletableFuture.supplyAsync(() -> "One"));
futures.add(CompletableFuture.supplyAsync(() -> "Two"));
futures.add(CompletableFuture.supplyAsync(() -> "Three"));
futures.add(CompletableFuture.supplyAsync(() -> { throw new RuntimeException("Sucks to be four"); }));
futures.add(cs.submit(() -> "Five"));

//places all futures in completion service queue
tasks.addAll(futures);

List<String> successes = new ArrayList<>();
List<String> failures = new ArrayList<>();

while (futures.size() > 0) {
    Future<String> f = cs.poll();
    if (f != null) {
        futures.remove(f);
        try {
            //at this point the future is guaranteed to be solved
            //so there won't be any blocking here
            String value = f.get();
            successes.add(value);
        } catch (Exception e) {
            failures.add(e.getMessage());
        }
    }
}
7
Edwin Dalorzo