web-dev-qa-db-fra.com

En attente d'une liste d'avenir

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.
88
user93796

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/

97
dcernahoschi

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

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).

15
jmiserez

Utilisez CompletableFutureen 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());
4
sendon1982

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/

1
Bohao LI

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.

0
user2511414

Si vous utilisez Java 8 et que vous ne voulez pas manipuler CompletableFutures, 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;
    }
}
0
fl0w
 /**
     * 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());
    };
0
Mohamed.Abdo

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