È possibile utilizzare il seguente metodo di supporto:
public static <T>
CompletableFuture<T> anyOf(List<? extends CompletionStage<? extends T>> l) {
CompletableFuture<T> f=new CompletableFuture<>();
Consumer<T> complete=f::complete;
l.forEach(s -> s.thenAccept(complete));
return f;
}
cui si può utilizzare in questo modo, per dimostrare che ignorerà le eccezioni precedenti ma restituirà il primo valore fornito:
List<CompletableFuture<String>> futures = Arrays.asList(
CompletableFuture.supplyAsync(
() -> { throw new RuntimeException("failing immediately"); }
),
CompletableFuture.supplyAsync(
() -> { LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));
return "with 5s delay";
}),
CompletableFuture.supplyAsync(
() -> { LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(10));
return "with 10s delay";
})
);
CompletableFuture<String> c = anyOf(futures);
logger.info(c.join());
Uno svantaggio di questa soluzione è che sarà mai completo se tutti i futures completano eccezionalmente. Una soluzione, che fornirà il primo valore se esiste una computazione successo ma sicuro eccezionalmente se non c'è calcolo successo a tutti, è un po 'più complesso:
public static <T>
CompletableFuture<T> anyOf(List<? extends CompletionStage<? extends T>> l) {
CompletableFuture<T> f=new CompletableFuture<>();
Consumer<T> complete=f::complete;
CompletableFuture.allOf(
l.stream().map(s -> s.thenAccept(complete)).toArray(CompletableFuture<?>[]::new)
).exceptionally(ex -> { f.completeExceptionally(ex); return null; });
return f;
}
Esso utilizza il fatto che gestore eccezionalmente allOf
s' viene invocato solo dopo che tutti i futures sono stati completati (in via eccezionale o meno) e che un futuro può essere completato una sola volta (lasciando da parte cose speciali come obtrude…
). Quando viene eseguito l'eccezionalmente gestore, qualsiasi tentativo di completare il futuro con un risultato è stato eseguito, se ce n'era uno, quindi il tentativo di completarlo eccezionalmente ha esito positivo solo se non è stato completato con successo.
Può essere utilizzato esattamente nello stesso modo come la prima soluzione e mostrano solo il comportamento diverso se tutti i calcoli falliscono, ad esempio:
List<CompletableFuture<String>> futures = Arrays.asList(
CompletableFuture.supplyAsync(
() -> { throw new RuntimeException("failing immediately"); }
),
CompletableFuture.supplyAsync(
// delayed to demonstrate that the solution will wait for all completions
// to ensure it doesn't miss a possible successful computation
() -> { LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));
throw new RuntimeException("failing later"); }
)
);
CompletableFuture<String> c = anyOf(futures);
try { logger.info(c.join()); }
catch(CompletionException ex) { logger.severe(ex.toString()); }
L'esempio precedente utilizza un ritardo dimostrando che la soluzione attenderà tutti completamenti quando non c'è successo, mentre this example on ideone dimostrerà come un successo successivo trasformerà il risultato in successo. Nota che a causa della memorizzazione dei risultati nella cache di Ideones potresti non notare il ritardo.
Si noti che nel caso in cui tutti i futures falliscano, non c'è garanzia su quale delle eccezioni verrà segnalata. Dal momento che attende tutti i completamenti nel caso errato, ognuno potrebbe arrivare al risultato finale.
Cerchiamo [Continuiamo questa discussione in videochat] (http://chat.stackoverflow.com/rooms/97948/discussion-between-basilevs-and-holger). – Basilevs
@Basilevs: ho ampliato la risposta – Holger