Amélioration de allOf et anyOf dans CompletableFuture



Bonjour encore. En prévision du début du cours, "Java Developer" a préparé une traduction de matériel utile pour vous.


Il existe deux méthodes dans CompletableFuture dont la conception me surprend:

  • CompletableFuture # allOf
  • CompletableFuture # anyOf


Dans cet article, nous verrons ce qui ne va pas avec eux et comment les rendre plus pratiques.

CompletableFuture # allOf


Regardons la signature de la méthode:

 public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) { // ... } 


Il y a au moins deux questions litigieuses ici:

  1. La méthode accepte plusieurs objets CompletableFuture qui renvoient des objets de différents types.
  2. La méthode renvoie CompletableFuture , qui renvoie Void


De plus, certains peuvent ne pas aimer le nombre variable de paramètres, alors regardons également cette partie.

CompletableFuture<Void> souvent utilisé comme signal pour terminer une opération; cependant, en apportant une petite modification à l'API, cette méthode peut être utilisée à la fois comme dispositif de signalisation et comme vecteur des résultats de toutes les opérations terminées . Essayons de le faire.

CompletableFuture asynchrone # allOf


Tout d'abord, trouvons la bonne signature.

Il est juste de supposer que dans la plupart des cas, le traitement d'une liste de CompletableFuture homogène et le renvoi d'un CompletableFuture contenant une liste de résultats seront nécessaires:

 public static <T> CompletableFuture<List<T>> allOf( Collection<CompletableFuture<T>> futures) { // ... } 


Les entrailles de la méthode originale sont probablement plus complexes que vous ne le pensez:

 static CompletableFuture<Void> andTree( CompletableFuture<?>[] cfs, int lo, int hi) { CompletableFuture<Void> d = new CompletableFuture<Void>(); if (lo > hi) // empty d.result = NIL; else { CompletableFuture<?> a, b; int mid = (lo + hi) >>> 1; if ((a = (lo == mid ? cfs[lo] : andTree(cfs, lo, mid))) == null || (b = (lo == hi ? a : (hi == mid+1) ? cfs[hi] : andTree(cfs, mid+1, hi))) == null) throw new NullPointerException(); if (!d.biRelay(a, b)) { BiRelay<?,?> c = new BiRelay<>(d, a, b); a.bipush(b, c); c.tryFire(SYNC); } } return d; } 


Par conséquent, au lieu de le créer à partir de zéro, nous essaierons de réutiliser ce qui est déjà dans la méthode d'origine comme s'il était destiné à être utilisé comme un dispositif de signalisation d'achèvement ... puis de simplement changer le résultat de l'annulation dans la future liste:

 CompletableFuture<List<CompletableFuture<T>>> i = futures.stream() .collect(collectingAndThen( toList(), l -> CompletableFuture.allOf(l.toArray(new CompletableFuture[0])) .thenApply(__ -> l))); 


Jusqu'ici tout va bien. Nous avons réussi à obtenir
CompletableFuture<List<CompletableFuture<T> > > CompletableFuture<Void> au lieu de CompletableFuture<Void> , qui est déjà bon. Mais nous n'avons pas besoin d'une liste d'avenir avec des résultats, nous avons besoin d'une liste de résultats.

Maintenant, nous pouvons simplement traiter la liste et en retirer tout avenir indésirable. Il est parfaitement normal d'appeler les méthodes de jointure CompletableFuture #, car nous savons qu'elles ne seront jamais bloquées (à ce stade, tous les futurs sont déjà terminés):

 CompletableFuture<List<T>> result = intermediate .thenApply(list -> list.stream() .map(CompletableFuture::join) .collect(toList())); 


Maintenant, combinons tout cela dans une solution finale:

 public static <T> CompletableFuture<List<T>> allOf( Collection<CompletableFuture<T>> futures) { return futures.stream() .collect(collectingAndThen( toList(), l -> CompletableFuture.allOf(l.toArray(new CompletableFuture[0])) .thenApply(__ -> l.stream() .map(CompletableFuture::join) .collect(Collectors.toList())))); } 


Asynchrone et Falling CompletableFuture # allOf


S'il existe des exceptions, le CompletableFuture # allOf d'origine attend que toutes les opérations restantes se terminent.

Et si nous voulons signaler la fin de l'opération lorsqu'une exception s'y produit, nous devrons alors modifier l'implémentation.

Pour ce faire, créez une nouvelle instance de CompletableFuture et CompletableFuture -la manuellement après qu'une des opérations déclenche une exception:

 CompletableFuture<List<T>> result = new CompletableFuture<>(); futures.forEach(f -> f .handle((__, ex) -> ex == null || result.completeExceptionally(ex))); 


... mais alors nous devons faire face au scénario lorsque tout l'avenir sera achevé avec succès. Cela peut être facilement fait en utilisant la méthode améliorée allOf (), puis mettez simplement fin au futur manuellement:

 allOf(futures).thenAccept(result::complete); 


Maintenant, nous pouvons tout combiner pour former la solution finale:

 public static <T> CompletableFuture<List<T>> allOfShortcircuiting(Collection<CompletableFuture<T>> futures) { CompletableFuture<List<T>> result = new CompletableFuture<>(); for (CompletableFuture<?> f : futures) { f.handle((__, ex) -> ex == null || result.completeExceptionally(ex)); } allOf(futures).thenAccept(result::complete); return result; } 


CompletableFuture # anyOf


Commençons par la signature de la méthode:

 public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) { // ... } 


Nous pouvons détecter immédiatement les mêmes problèmes qu'avec la méthode discutée ci-dessus:

  1. La méthode accepte plusieurs objets CompletableFuture contenant des objets de différents types.
  2. La méthode renvoie un CompletableFuture contenant un objet de type Object .


Pour autant que je CompletableFuture#allOf été conçue pour être utilisée comme dispositif de signalisation. Mais CompletableFuture#anyOf ne suit pas cette philosophie, renvoyant un CompletableFuture<Object> , ce qui est encore plus déroutant.

Jetez un œil à l'exemple suivant où j'essaie de traiter CompletableFuture contenant des données de différents types:

 CompletableFuture<Integer> f1 = CompletableFuture.completedFuture(1); CompletableFuture<String> f2 = CompletableFuture.completedFuture("2"); Integer result = CompletableFuture.anyOf(f1, f2) .thenApply(r -> { if (r instanceof Integer) { return (Integer) r; } else if (r instanceof String) { return Integer.valueOf((String) r); } throw new IllegalStateException("unexpected object type!"); }).join(); 


Assez mal à l'aise, non?

Heureusement, cela est assez facile à adapter pour un scénario plus courant (en attendant l'un des nombreux futurs contenant des valeurs du même type) en changeant la signature et en introduisant un transtypage de type direct.

Ainsi, grâce à nos améliorations, nous pouvons réutiliser les méthodes existantes et apporter le résultat en toute sécurité:

 public static <T> CompletableFuture<T> anyOf(List<CompletableFuture<T>> cfs) { return CompletableFuture.anyOf(cfs.toArray(new CompletableFuture[0])) .thenApply(o -> (T) o); } public static <T> CompletableFuture<T> anyOf(CompletableFuture<T>... cfs) { return CompletableFuture.anyOf(cfs).thenApply(o -> (T) o); } 


Code source


Le code source peut être trouvé sur Github .

C’est tout. Rendez-vous sur le parcours .

Source: https://habr.com/ru/post/fr481804/


All Articles