J'ai une méthode qui retourne une List
de futures
List<Future<O>> futures = getFutures();
Maintenant, je veux attendre que tous les futurs soient correctement traités ou que l'une des tâches dont la sortie est renvoyée par une future lève une exception. Même si une tâche lève une exception, il est inutile d'attendre les autres futurs.
Une approche simple consisterait à
wait() {
For(Future f : futures) {
try {
f.get();
} catch(Exception e) {
//TODO catch specific exception
// this future threw exception , means somone could not do its task
return;
}
}
}
Mais le problème ici est que si, par exemple, le 4ème avenir lève une exception, j'attendrai inutilement que les 3 premiers futurs soient disponibles.
Comment résoudre ceci? Compte à rebours aide-t-il de quelque façon que ce soit Je ne peux pas utiliser Future isDone
car le document Java dit
boolean isDone()
Returns true if this task completed. Completion may be due to normal termination, an exception, or cancellation -- in all of these cases, this method will return true.
Vous pouvez utiliser un CompletionService pour recevoir les futures dès qu’ils sont prêts et si l’un d’eux lève une exception annule le traitement. Quelque chose comme ça:
Executor executor = Executors.newFixedThreadPool(4);
CompletionService<SomeResult> completionService =
new ExecutorCompletionService<SomeResult>(executor);
//4 tasks
for(int i = 0; i < 4; i++) {
completionService.submit(new Callable<SomeResult>() {
public SomeResult call() {
...
return result;
}
});
}
int received = 0;
boolean erros = false;
while(received < 4 && !errors) {
Future<SomeResult> resultFuture = completionService.take(); //blocks if none available
try {
SomeResult result = resultFuture.get();
received ++;
... // do something with the result
}
catch(Exception e) {
//log
errors = true;
}
}
Je pense que vous pouvez encore améliorer pour annuler toutes les tâches en cours d'exécution si l'une d'elles génère une erreur.
EDIT: J'ai trouvé un exemple plus complet ici: http://blog.teamlazerbeez.com/2009/04/29/Java-completionservice/
Si vous utilisez Java 8, vous pouvez le faire plus facilement avec CompletableFuture et CompletableFuture.allOf , qui applique le rappel uniquement après que toutes les CompletableFutures fournies ont été effectuées.
// Waits for *all* futures to complete and returns a list of results.
// If *any* future completes exceptionally then the resulting future will also complete exceptionally.
public static <T> CompletableFuture<List<T>> all(List<CompletableFuture<T>> futures) {
CompletableFuture[] cfs = futures.toArray(new CompletableFuture[futures.size()]);
return CompletableFuture.allOf(cfs)
.thenApply(() -> futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList())
);
}
Vous pouvez utiliser un ExecutorCompletionService . La documentation contient même un exemple pour votre cas d'utilisation exact:
Supposons plutôt que vous souhaitiez utiliser le premier résultat non nul de l'ensemble de tâches, en ignorant toutes les exceptions rencontrées et en annulant toutes les autres tâches lorsque la première est prête:
void solve(Executor e, Collection<Callable<Result>> solvers) throws InterruptedException {
CompletionService<Result> ecs = new ExecutorCompletionService<Result>(e);
int n = solvers.size();
List<Future<Result>> futures = new ArrayList<Future<Result>>(n);
Result result = null;
try {
for (Callable<Result> s : solvers)
futures.add(ecs.submit(s));
for (int i = 0; i < n; ++i) {
try {
Result r = ecs.take().get();
if (r != null) {
result = r;
break;
}
} catch (ExecutionException ignore) {
}
}
} finally {
for (Future<Result> f : futures)
f.cancel(true);
}
if (result != null)
use(result);
}
La chose importante à noter ici est que ecs.take () aura la première tâche terminée, pas seulement la première soumise. Ainsi, vous devriez les obtenir dans l’ordre de terminer l’exécution (ou de lancer une exception).
Utilisez CompletableFuture
en Java 8
// Kick of multiple, asynchronous lookups
CompletableFuture<User> page1 = gitHubLookupService.findUser("Test1");
CompletableFuture<User> page2 = gitHubLookupService.findUser("Test2");
CompletableFuture<User> page3 = gitHubLookupService.findUser("Test3");
// Wait until they are all done
CompletableFuture.allOf(page1,page2,page3).join();
logger.info("--> " + page1.get());
Si vous souhaitez combiner une liste de CompletableFutures, vous pouvez procéder comme suit:
List<CompletableFuture<Void>> futures = new ArrayList<>();
// ... Add futures to this ArrayList of CompletableFutures
// CompletableFuture.allOf() method demand a variadic arguments
// You can use this syntax to pass a List instead
CompletableFuture<Void> allFutures = CompletableFuture.allOf(
futures.toArray(new CompletableFuture[futures.size()]));
// Wait for all individual CompletableFuture to complete
// All individual CompletableFutures are executed in parallel
allFutures.get();
Pour plus de détails sur Future & CompletableFuture, liens utiles:
1. Future: https://www.baeldung.com/Java-future
2. CompletableFuture: https://www.baeldung.com/Java-completablefuture
3. CompletableFuture: https://www.callicoder.com/Java-8-completablefuture-tutorial/
peut-être que cela aiderait (rien ne serait remplacé par du fil brut, ouais!) Je suggère d'exécuter chaque gars Future
avec un fil séparé (ils sont parallèles), puis, chaque fois que l'une des erreurs se produit, cela signale le gestionnaire (Handler
class).
class Handler{
//...
private Thread thisThread;
private boolean failed=false;
private Thread[] trds;
public void waitFor(){
thisThread=Thread.currentThread();
List<Future<Object>> futures = getFutures();
trds=new Thread[futures.size()];
for (int i = 0; i < trds.length; i++) {
RunTask rt=new RunTask(futures.get(i), this);
trds[i]=new Thread(rt);
}
synchronized (this) {
for(Thread tx:trds){
tx.start();
}
}
for(Thread tx:trds){
try {tx.join();
} catch (InterruptedException e) {
System.out.println("Job failed!");break;
}
}if(!failed){System.out.println("Job Done");}
}
private List<Future<Object>> getFutures() {
return null;
}
public synchronized void cancelOther(){if(failed){return;}
failed=true;
for(Thread tx:trds){
tx.stop();//Deprecated but works here like a boss
}thisThread.interrupt();
}
//...
}
class RunTask implements Runnable{
private Future f;private Handler h;
public RunTask(Future f,Handler h){this.f=f;this.h=h;}
public void run(){
try{
f.get();//beware about state of working, the stop() method throws ThreadDeath Error at any thread state (unless it blocked by some operation)
}catch(Exception e){System.out.println("Error, stopping other guys...");h.cancelOther();}
catch(Throwable t){System.out.println("Oops, some other guy has stopped working...");}
}
}
Je dois dire que le code ci-dessus serait une erreur (n'a pas vérifié), mais j'espère pouvoir expliquer la solution. S'il vous plaît essayer.
Si vous utilisez Java 8 et que vous ne voulez pas manipuler CompletableFuture
s, j'ai écrit un outil permettant de récupérer les résultats d'un List<Future<T>>
à l'aide de la diffusion en continu. La clé est qu'il vous est interdit de map(Future::get)
pendant le lancement.
public final class Futures
{
private Futures()
{}
public static <E> Collector<Future<E>, Collection<E>, List<E>> present()
{
return new FutureCollector<>();
}
private static class FutureCollector<T> implements Collector<Future<T>, Collection<T>, List<T>>
{
private final List<Throwable> exceptions = new LinkedList<>();
@Override
public Supplier<Collection<T>> supplier()
{
return LinkedList::new;
}
@Override
public BiConsumer<Collection<T>, Future<T>> accumulator()
{
return (r, f) -> {
try
{
r.add(f.get());
}
catch (InterruptedException e)
{}
catch (ExecutionException e)
{
exceptions.add(e.getCause());
}
};
}
@Override
public BinaryOperator<Collection<T>> combiner()
{
return (l1, l2) -> {
l1.addAll(l2);
return l1;
};
}
@Override
public Function<Collection<T>, List<T>> finisher()
{
return l -> {
List<T> ret = new ArrayList<>(l);
if (!exceptions.isEmpty())
throw new AggregateException(exceptions, ret);
return ret;
};
}
@Override
public Set<Java.util.stream.Collector.Characteristics> characteristics()
{
return Java.util.Collections.emptySet();
}
}
Cela nécessite une AggregateException
qui fonctionne comme C #
public class AggregateException extends RuntimeException
{
/**
*
*/
private static final long serialVersionUID = -4477649337710077094L;
private final List<Throwable> causes;
private List<?> successfulElements;
public AggregateException(List<Throwable> causes, List<?> l)
{
this.causes = causes;
successfulElements = l;
}
public AggregateException(List<Throwable> causes)
{
this.causes = causes;
}
@Override
public synchronized Throwable getCause()
{
return this;
}
public List<Throwable> getCauses()
{
return causes;
}
public List<?> getSuccessfulElements()
{
return successfulElements;
}
public void setSuccessfulElements(List<?> successfulElements)
{
this.successfulElements = successfulElements;
}
}
Ce composant agit exactement comme C/s Task.WaitAll . Je travaille sur une variante qui fait la même chose que CompletableFuture.allOf
(équivalent à Task.WhenAll
)
La raison pour laquelle j'ai fait cela est que j'utilise la variable ListenableFuture
de Spring et que je ne veux pas porter à CompletableFuture
alors que c'est une méthode plus standard
CompletionService prendra vos Callables avec la méthode .submit () et vous pourrez récupérer les contrats à terme calculés avec la méthode .take ().
Une chose à ne pas oublier est de mettre fin à ExecutorService en appelant la méthode .shutdown (). De plus, vous ne pouvez appeler cette méthode que lorsque vous avez enregistré une référence dans le service de l'exécuteur. Veillez donc à en conserver une.
Exemple de code - Pour un nombre fixe d’éléments de travail à travailler en parallèle:
ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
CompletionService<YourCallableImplementor> completionService =
new ExecutorCompletionService<YourCallableImplementor>(service);
ArrayList<Future<YourCallableImplementor>> futures = new ArrayList<Future<YourCallableImplementor>>();
for (String computeMe : elementsToCompute) {
futures.add(completionService.submit(new YourCallableImplementor(computeMe)));
}
//now retrieve the futures after computation (auto wait for it)
int received = 0;
while(received < elementsToCompute.size()) {
Future<YourCallableImplementor> resultFuture = completionService.take();
YourCallableImplementor result = resultFuture.get();
received ++;
}
//important: shutdown your ExecutorService
service.shutdown();
Exemple de code - Pour un nombre dynamique d'éléments de travail à travailler en parallèle:
public void runIt(){
ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
CompletionService<CallableImplementor> completionService = new ExecutorCompletionService<CallableImplementor>(service);
ArrayList<Future<CallableImplementor>> futures = new ArrayList<Future<CallableImplementor>>();
//Initial workload is 8 threads
for (int i = 0; i < 9; i++) {
futures.add(completionService.submit(write.new CallableImplementor()));
}
boolean finished = false;
while (!finished) {
try {
Future<CallableImplementor> resultFuture;
resultFuture = completionService.take();
CallableImplementor result = resultFuture.get();
finished = doSomethingWith(result.getResult());
result.setResult(null);
result = null;
resultFuture = null;
//After work package has been finished create new work package and add it to futures
futures.add(completionService.submit(write.new CallableImplementor()));
} catch (InterruptedException | ExecutionException e) {
//handle interrupted and assert correct thread / work packet count
}
}
//important: shutdown your ExecutorService
service.shutdown();
}
public class CallableImplementor implements Callable{
boolean result;
@Override
public CallableImplementor call() throws Exception {
//business logic goes here
return this;
}
public boolean getResult() {
return result;
}
public void setResult(boolean result) {
this.result = result;
}
}
/**
* execute suppliers as future tasks then wait / join for getting results
* @param functors a supplier(s) to execute
* @return a list of results
*/
private List getResultsInFuture(Supplier<?>... functors) {
CompletableFuture[] futures = stream(functors)
.map(CompletableFuture::supplyAsync)
.collect(Collectors.toList())
.toArray(new CompletableFuture[functors.length]);
CompletableFuture.allOf(futures).join();
return stream(futures).map(a-> {
try {
return a.get();
} catch (InterruptedException | ExecutionException e) {
//logger.error("an error occurred during runtime execution a function",e);
return null;
}
}).collect(Collectors.toList());
};
J'ai une classe d'utilitaire qui contient ceux-ci:
@FunctionalInterface
public interface CheckedSupplier<X> {
X get() throws Throwable;
}
public static <X> Supplier<X> uncheckedSupplier(final CheckedSupplier<X> supplier) {
return () -> {
try {
return supplier.get();
} catch (final Throwable checkedException) {
throw new IllegalStateException(checkedException);
}
};
}
Une fois que vous avez cela, en utilisant une importation statique, vous pouvez simplement attendre tous les futurs comme ceci:
futures.stream().forEach(future -> uncheckedSupplier(future::get).get());
vous pouvez également collecter tous leurs résultats comme ceci:
List<MyResultType> results = futures.stream()
.map(future -> uncheckedSupplier(future::get).get())
.collect(Collectors.toList());