2017-02-27 3 views
5

J'ai 3 CompletableFutures tous les 3 renvoyant différents types de données.Java 8 Futures pouvant être complétées tousDe différents types de données

Je cherche à créer un objet de résultat qui est une composition du résultat retourné par les 3 futurs.

donc mon code de travail actuel ressemble à ceci:

public ClassD getResultClassD() { 

    ClassD resultClass = new ClassD(); 
    CompletableFuture<ClassA> classAFuture = CompletableFuture.supplyAsync(() -> service.getClassA()); 
    CompletableFuture<ClassB> classBFuture = CompletableFuture.supplyAsync(() -> service.getClassB()); 
    CompletableFuture<ClassC> classCFuture = CompletableFuture.supplyAsync(() -> service.getClassC()); 

    CompletableFuture.allOf(classAFuture, classBFuture, classCFuture) 
        .thenAcceptAsync(it -> { 
         ClassA classA = classAFuture.join(); 
         if (classA != null) { 
          resultClass.setClassA(classA); 
         } 

         ClassB classB = classBFuture.join(); 
         if (classB != null) { 
          resultClass.setClassB(classB); 
         } 

         ClassC classC = classCFuture.join(); 
         if (classC != null) { 
          resultClass.setClassC(classC); 
         } 

        }); 

    return resultClass; 
} 

Mes questions sont les suivantes:

  1. Mon hypothèse est que depuis que je suis en utilisant allOf et thenAcceptAsync cet appel sera non blocage. Est-ce que je comprends bien?

  2. Est-ce la bonne façon de gérer des contrats à terme multiples renvoyant différents types de résultats?

  3. Est-il correct de construire l'objet dans thenAcceptAsync?

  4. Est-il approprié d'utiliser la méthode join ou getNow dans lambda thenAcceptAsync?

Répondre

6

Votre tentative va dans la bonne direction, mais pas correcte. Votre méthode getResultClassD() renvoie un objet déjà instancié de type sur lequel un thread arbitraire appellera des méthodes de modification, sans que l'appelant de getResultClassD() s'en aperçoive. Cela peut provoquer des conditions de concurrence, si les méthodes de modification ne sont pas thread safe par leurs propres moyens, en outre, l'appelant ne saura jamais, lorsque l'instance ClassD est réellement prêt à l'emploi.

Une bonne solution serait:

public CompletableFuture<ClassD> getResultClassD() { 

    CompletableFuture<ClassA> classAFuture 
     = CompletableFuture.supplyAsync(() -> service.getClassA()); 
    CompletableFuture<ClassB> classBFuture 
     = CompletableFuture.supplyAsync(() -> service.getClassB()); 
    CompletableFuture<ClassC> classCFuture 
     = CompletableFuture.supplyAsync(() -> service.getClassC()); 

    return CompletableFuture.allOf(classAFuture, classBFuture, classCFuture) 
     .thenApplyAsync(dummy -> { 
      ClassD resultClass = new ClassD(); 

      ClassA classA = classAFuture.join(); 
      if (classA != null) { 
       resultClass.setClassA(classA); 
      } 

      ClassB classB = classBFuture.join(); 
      if (classB != null) { 
       resultClass.setClassB(classB); 
      } 

      ClassC classC = classCFuture.join(); 
      if (classC != null) { 
       resultClass.setClassC(classC); 
      } 

      return resultClass; 
     }); 
} 

Maintenant, l'appelant de getResultClassD() peut utiliser le retour CompletableFuture pour interroger les actions dépendantes état d'avancement ou de chaîne ou d'utiliser join() pour récupérer le résultat, une fois l'opération terminée .

Pour répondre aux autres questions, oui, cette opération est asynchrone et l'utilisation de join() dans les expressions lambda est appropriée. join a été créé exactement parce que Future.get(), qui est déclaré lancer des exceptions vérifiées, rend l'utilisation de ces expressions lambda inutilement difficile.

Notez que les tests null sont seulement utiles, si ces service.getClassX() peuvent effectivement retourner null. Si l'un des appels de service échoue avec une exception, l'opération complète (représentée par CompletableFuture<ClassD>) se termine exceptionnellement.

+0

merci pour la réponse détaillée. Mon seul suivi à votre réponse est que thenApplyAsync a un type de retour de CompletableFuture , comment cela fonctionnerait-il ici et comment invoquer cette méthode et consommer le résultat? –

+4

Non, c'est le type de retour de 'allOf' qui est' CompletableFuture ' , c'est pourquoi la fonction passée 'thenApplyAsync' reçoit' Void' en entrée (le paramètre 'dummy' ci-dessus, au lieu de' dummy -> ', vous pouvez également écrire' (Void dummy) -> '). Ensuite, la fonction traduit l'entrée 'Void' (en l'ignorant réellement) en un résultat' ClassD', donc le résultat de 'thenApplyAsync' sera' CompletableFuture '. – Holger

+1

@Holger Je descendais un chemin similaire à vous mais j'utilisais Optional.ofNullable dans les appels de service, donc vous pouvez avoir 'cCFuture.join(). IfPresent (class :: SetStuff)' – Ash

3

Je descendais un chemin semblable à ce que @Holger faisait dans sa réponse, mais envelopper les appels de service en option, ce qui conduit à un code plus propre à l'étape thenApplyAsync

CompletableFuture<Optional<ClassA>> classAFuture 
    = CompletableFuture.supplyAsync(() -> Optional.ofNullable(service.getClassA()))); 

CompletableFuture<Optional<ClassB>> classBFuture 
    = CompletableFuture.supplyAsync(() -> Optional.ofNullable(service.getClassB())); 

CompletableFuture<Optional<ClassC>> classCFuture 
    = CompletableFuture.supplyAsync(() -> Optional.ofNullable(service.getClassC())); 

return CompletableFuture.allOf(classAFuture, classBFuture, classCFuture) 
    .thenApplyAsync(dummy -> { 
     ClassD resultClass = new ClassD(); 

     classAFuture.join().ifPresent(resultClass::setClassA) 
     classBFuture.join().ifPresent(resultClass::setClassB) 
     classCFuture.join().ifPresent(resultClass::setClassC) 

     return resultClass; 
    }); 
+1

oui en utilisant des options est la bonne façon d'y penser –

0

Une autre façon de gérer Si vous ne voulez pas déclarer autant de variables, vous devez utiliser thenCombine ou ensuiteCombineAsync pour enchaîner vos futures.Les getters seront toujours déclenchés de manière asynchrone et les résultats seront exécutés dans l'ordre. C'est fondamentalement une autre option de syntaxe pour obtenir le même résultat.

+0

C'est probablement une bonne approche s'il n'y a que 2 ou 3 futurs à combiner. Cependant vous devriez probablement partir d'un 'CompletableFuture.completedFuture (new ClassD())' car l'instanciation ne vaut probablement pas la peine d'être exécutée de manière asynchrone. En fait, vous pourriez même l'instancier dans un 'thenApply()' sur le premier futur. –