Alles und jedes in CompletableFuture verbessern



Hallo nochmal. Im Vorfeld des Kursbeginns hat "Java Developer" eine Übersetzung von nützlichem Material für Sie vorbereitet.


Es gibt zwei Methoden in CompletableFuture deren Design mich überrascht:

  • CompletableFuture # allOf
  • CompletableFuture # anyOf


In diesem Artikel werden wir sehen, was mit ihnen los ist und wie sie bequemer gemacht werden können.

CompletableFuture # allOf


Schauen wir uns die Methodensignatur an:

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


Hier gibt es mindestens zwei strittige Punkte:

  1. Die Methode akzeptiert mehrere CompletableFuture Objekte, die Objekte unterschiedlichen Typs zurückgeben.
  2. Die Methode gibt CompletableFuture , die Void zurückgibt


Einige mögen möglicherweise auch die variable Anzahl der Parameter nicht, also schauen wir uns auch diesen Teil an.

CompletableFuture<Void> häufig als Signal zum Abschließen einer Operation verwendet. Durch eine geringfügige Änderung der API kann diese Methode jedoch sowohl als Signalgeber als auch als Träger der Ergebnisse aller abgeschlossenen Operationen verwendet werden . Lass es uns versuchen.

Asynchronous CompletableFuture # allOf


Lassen Sie uns zuerst die richtige Signatur erstellen.

Es ist anzunehmen, dass in den meisten Fällen die Verarbeitung einer Liste homogener CompletableFuture und die Rückgabe einer CompletableFuture mit einer Liste von Ergebnissen erforderlich sind:

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


Die Innereien der ursprünglichen Methode sind höchstwahrscheinlich komplexer als erwartet:

 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; } 


Anstatt es von Grund auf neu zu erstellen, werden wir versuchen, das, was bereits in der ursprünglichen Methode enthalten ist, so wiederzuverwenden, als ob es als Abschlusssignalgeber verwendet werden sollte. Ändern Sie dann einfach das Ergebnis für ungültig in die zukünftige Liste:

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


So weit so gut. Wir haben es geschafft
CompletableFuture<List<CompletableFuture<T> > > CompletableFuture<Void> anstelle von CompletableFuture<Void> , was bereits gut ist. Aber wir brauchen keine Liste der Zukunft mit Ergebnissen, wir brauchen eine Liste der Ergebnisse.

Jetzt können wir die Liste einfach verarbeiten und unerwünschte Zukunft daraus entfernen. Es ist völlig normal, CompletableFuture # Join-Methoden aufzurufen, da wir wissen, dass sie niemals blockiert werden (zu diesem Zeitpunkt sind alle zukünftigen Methoden bereits abgeschlossen):

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


Kombinieren wir dies nun zu einer endgültigen Lösung:

 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())))); } 


Asynchron und fallend CompletableFuture # allOf


Wenn es Ausnahmen gibt, wartet die ursprüngliche CompletableFuture # allOf, bis alle verbleibenden Vorgänge abgeschlossen sind.

Und wenn wir den Abschluss des Vorgangs melden möchten, wenn eine Ausnahme auftritt, müssen wir die Implementierung ändern.

Erstellen Sie dazu eine neue Instanz von CompletableFuture und beenden Sie sie manuell, nachdem eine der Operationen eine Ausnahme ausgelöst hat:

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


... aber dann müssen wir uns mit dem Szenario befassen, wenn alle zukünftigen erfolgreich abgeschlossen werden. Dies kann einfach mit der verbesserten allOf () -Methode durchgeführt werden und schließt die Zukunft dann einfach manuell ab:

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


Jetzt können wir alles miteinander kombinieren, um die endgültige Lösung zu erhalten:

 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


Beginnen wir mit der Methodensignatur:

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


Wir können sofort die gleichen Probleme wie bei der oben diskutierten Methode feststellen:

  1. Die Methode akzeptiert mehrere CompletableFuture Objekte, die Objekte unterschiedlichen Typs enthalten.
  2. Die Methode gibt eine CompletableFuture die ein Objekt vom Typ Object .


Soweit ich CompletableFuture#allOf wurde die CompletableFuture#allOf als Signalgeber konzipiert. CompletableFuture#anyOf folgt jedoch nicht dieser Philosophie und gibt ein CompletableFuture<Object> , was noch verwirrender ist.

Schauen Sie sich das folgende Beispiel an, in dem ich versuche, CompletableFuture mit Daten verschiedener Typen zu verarbeiten:

 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(); 


Ziemlich unangenehm, nicht wahr?

Glücklicherweise ist dies für ein häufigeres Szenario (Warten auf eines von vielen zukünftigen Szenarios mit Werten desselben Typs) recht einfach anzupassen, indem die Signatur geändert und das direkte Typ-Casting eingeführt wird.

So können wir mit unseren Verbesserungen vorhandene Methoden wiederverwenden und das Ergebnis sicher bringen:

 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); } 


Quellcode


Der Quellcode ist auf Github zu finden.

Das ist alles. Wir sehen uns auf dem Kurs .

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


All Articles