En attente d'une liste d'avenir


145

J'ai une méthode qui renvoie un Listdes futurs

List<Future<O>> futures = getFutures();

Maintenant, je veux attendre que tous les futurs soient traités avec succès ou que l'une des tâches dont la sortie est retournée par un futur lève une exception. Même si une tâche lève une exception, il ne sert à rien d'attendre les autres futurs.

Une approche simple serait de

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 futur lève une exception, alors j'attendrai inutilement que les 3 premiers futurs soient disponibles.

Comment résoudre ça? Le compte à rebours du verrou vous aidera-t-il d'une manière ou d'une autre? Je ne peux pas utiliser Future isDonecar 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.

1
qui génère ces futurs? De quel type sont-ils? L'interface java.util.concurrent.Future ne fournit pas les fonctionnalités que vous souhaitez, le seul moyen est d'utiliser vos propres Futures avec des rappels.
Alexei Kaigorodov

Vous pouvez créer une instance de ExecutionServicepour chaque "lot" de tâches, les lui soumettre, puis arrêter immédiatement le service et l'utiliser awaitTermination(), je suppose.
millimoose

Vous pouvez utiliser un CountDownLatchsi vous enveloppez le corps de tous vos futurs dans un try..finallypour vous assurer que le loquet est également décrémenté.
millimoose

docs.oracle.com/javase/7/docs/api/java/util/concurrent/… fait exactement ce dont vous avez besoin.
assylias

@AlexeiKaigorodov OUI, mes futurs sont de type java.util.concurrent.Je poursuive l'avenir avec callable.J'obtiens Futture quand je soumets une tâche à un execureservice
user93796

Réponses:


124

Vous pouvez utiliser un CompletionService pour recevoir les contrats à terme dès qu'ils sont prêts et si l'un d'eux émet une exception, annulez 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 errors = 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'entre elles génère une erreur.


1
: Votre code a le même problème que celui que j'ai mentionné dans mon article.Si le futur lève une exception, le code attendra toujours que le futur 1,2,3 se termine. ou est-ce que completionSerice.take) retournera le futur qui se termine en premier?
user93796

1
Puis-je dire au service d'achèvement d'attendre X secondes au maximum?
user93796

1
Ne devrait pas avoir. Il n'itère pas sur les futurs, mais dès que l'on est prêt, il est traité / vérifié s'il n'est pas lancé d'exception.
dcernahoschi

2
Pour expirer le délai d'attente d'un futur dans la file d'attente, il existe une méthode d'interrogation (secondes) sur le CompletionService.
dcernahoschi

Voici l'exemple de travail sur github: github.com/princegoyal1987/FutureDemo
user18853

107

Si vous utilisez Java 8, vous pouvez le faire plus facilement avec CompletableFuture et CompletableFuture.allOf , qui n'appliquent le rappel qu'une fois que tous les CompletableFutures fournis sont terminés.

// 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(ignored -> futures.stream()
                                    .map(CompletableFuture::join)
                                    .collect(Collectors.toList())
            );
}

3
Bonjour @Andrejs, pourriez-vous s'il vous plaît expliquer ce que fait cet extrait de code. Je vois cela suggéré à plusieurs endroits mais je suis confus quant à ce qui se passe réellement. Comment les exceptions sont-elles gérées si l'un des threads échoue?
VSEWHGHP

2
@VSEWHGHP Depuis le javadoc: Si l'un des CompletableFutures donnés se termine exceptionnellement, le CompletableFuture retourné le fait également, avec une CompletionException tenant cette exception comme cause.
Andrejs

1
Bon, donc je faisais un suivi là-dessus, y a-t-il un moyen d'utiliser cet extrait de code mais d'obtenir les valeurs de tous les autres threads qui se sont terminés avec succès? Dois-je simplement parcourir la liste CompletableFutures et appeler get en ignorant CompletableFuture <List <T>> puisque la fonction de séquence prend soin de s'assurer que tous les threads sont complets avec résultat ou exception?
VSEWHGHP

6
Cela résout un problème différent. Si vous avez des Futureinstances, vous ne pouvez pas appliquer cette méthode. Ce n'est pas facile de se convertir Futureen CompletableFuture.
Jarekczek

cela ne fonctionnera pas si nous avons une exception dans certaines tâches.
slisnychyi

21

Utiliser un CompletableFuturedans 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());

1
Cela devrait être la réponse acceptée. Cela fait également partie de la documentation officielle du printemps: spring.io/guides/gs/async-method
maaw

Fonctionne comme prévu.
Dimon

15

Vous pouvez utiliser un ExecutorCompletionService . La documentation a 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 celles qui rencontrent des exceptions 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 () obtiendra 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).


3

Si vous utilisez Java 8 et que vous ne voulez pas manipuler CompletableFutures, j'ai écrit un outil pour récupérer les résultats pour une List<Future<T>>utilisation du streaming. La clé est qu'il vous est interdit de map(Future::get)lancer.

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 un AggregateExceptionqui 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 Task.WaitAll de C # . 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 Spring ListenableFutureet que je ne veux pas porter sur CompletableFuturemalgré que ce soit un moyen plus standard


1
Votez pour avoir vu la nécessité d'une AggregateException équivalente.
granadaCoder

Un exemple d'utilisation de cette installation serait bien.
XDS

1

Dans le cas où vous souhaitez combiner une liste de CompletableFutures, vous pouvez le faire:

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/


0

peut-être que cela aiderait (rien ne serait remplacé par un fil brut, ouais!) Je suggère d'exécuter chaque Futuregars avec un fil séparé (ils vont en parallèle), puis chaque fois qu'une erreur a eu lieu, cela signale simplement le gestionnaire ( Handlerclasse).

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 été vérifié), mais j'espère pouvoir expliquer la solution. Veuillez essayer.


0
 /**
     * 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

Le CompletionService prendra vos Callables avec la méthode .submit () et vous pourrez récupérer les futurs calculés avec la méthode .take ().

Une chose que vous ne devez 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 au service exécuteur, alors assurez-vous d'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

J'ai une classe 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());

Je revisite simplement mon ancien message et remarquez que vous avez eu un autre chagrin:

Mais le problème ici est que si, par exemple, le 4ème futur lève une exception, alors j'attendrai inutilement que les 3 premiers futurs soient disponibles.

Dans ce cas, la solution simple est de le faire en parallèle:

futures.stream().parallel()
 .forEach(future -> uncheckedSupplier(future::get).get());

De cette façon, la première exception, même si elle n'arrêtera pas l'avenir, cassera l'instruction forEach, comme dans l'exemple série, mais comme tous attendent en parallèle, vous n'aurez pas à attendre que les 3 premiers se terminent.


0
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Stack2 {   
    public static void waitFor(List<Future<?>> futures) {
        List<Future<?>> futureCopies = new ArrayList<Future<?>>(futures);//contains features for which status has not been completed
        while (!futureCopies.isEmpty()) {//worst case :all task worked without exception, then this method should wait for all tasks
            Iterator<Future<?>> futureCopiesIterator = futureCopies.iterator();
            while (futureCopiesIterator.hasNext()) {
                Future<?> future = futureCopiesIterator.next();
                if (future.isDone()) {//already done
                    futureCopiesIterator.remove();
                    try {
                        future.get();// no longer waiting
                    } catch (InterruptedException e) {
                        //ignore
                        //only happen when current Thread interrupted
                    } catch (ExecutionException e) {
                        Throwable throwable = e.getCause();// real cause of exception
                        futureCopies.forEach(f -> f.cancel(true));//cancel other tasks that not completed
                        return;
                    }
                }
            }
        }
    }
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        Runnable runnable1 = new Runnable (){
            public void run(){
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                }
            }
        };
        Runnable runnable2 = new Runnable (){
            public void run(){
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                }
            }
        };


        Runnable fail = new Runnable (){
            public void run(){
                try {
                    Thread.sleep(1000);
                    throw new RuntimeException("bla bla bla");
                } catch (InterruptedException e) {
                }
            }
        };

        List<Future<?>> futures = Stream.of(runnable1,fail,runnable2)
                .map(executorService::submit)
                .collect(Collectors.toList());

        double start = System.nanoTime();
        waitFor(futures);
        double end = (System.nanoTime()-start)/1e9;
        System.out.println(end +" seconds");

    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.