In attesa di un elenco di futuro


145

Ho un metodo che restituisce un Listfuturo

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

Ora voglio aspettare fino a quando tutti i futures non sono stati elaborati correttamente o una delle attività il cui output viene restituito da un futuro genera un'eccezione. Anche se un'attività genera un'eccezione, non ha senso aspettare gli altri futuri.

Un approccio semplice sarebbe

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

Ma il problema qui è se, ad esempio, il 4 ° futuro genera un'eccezione, quindi aspetterò inutilmente che i primi 3 futuri siano disponibili.

Come risolverlo? Il conto alla rovescia aiuterà in qualche modo? Non riesco a usare Future isDoneperché dice il documento java

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
chi genera quei futuri? Di che tipo sono? L'interfaccia java.util.concurrent.Future non fornisce le funzionalità desiderate, l'unico modo è utilizzare i propri Future con callback.
Alexei Kaigorodov,

Potresti creare un'istanza di ExecutionServiceper ogni "batch" di attività, inviarle ad essa, quindi chiudere immediatamente il servizio e utilizzarlo awaitTermination()su di esso, suppongo.
millimoose,

Puoi usare a CountDownLatchse avessi avvolto il corpo di tutti i tuoi futures in a try..finallyper assicurarti che anche il chiavistello venga diminuito.
millimoose,


@AlexeiKaigorodov SÌ, il mio futuro è di tipo java.util.concurrent. Faccio causa per il futuro con callable. Ricevo Futture quando invio un'attività a un servizio di esecuzione
user93796

Risposte:


124

È possibile utilizzare un CompletionService per ricevere i futures non appena sono pronti e se uno di essi genera un'eccezione, annullare l'elaborazione. Qualcosa come questo:

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

Penso che puoi migliorare ulteriormente per annullare qualsiasi attività ancora in esecuzione se uno di loro genera un errore.


1
: Il tuo codice ha lo stesso problema che ho menzionato nel mio post. Se il futuro genera un'eccezione, il codice attenderà ancora il completamento di 1,2,3 futuri. o sarà completatoSerice.take) restituirà il futuro che completa per primo?
user93796

1
Che dire dei timeout? Posso dire al servizio di completamento di attendere X secondi al massimo?
user93796

1
Non avrebbe dovuto. Non scorre sui futuri, ma non appena uno è pronto viene elaborato / verificato se non generato un'eccezione.
dcernahoschi,

2
Per il timeout in attesa che un futuro appaia in coda c'è un metodo di polling (secondi) su CompletionService.
dcernahoschi,

Ecco l'esempio funzionante su github: github.com/princegoyal1987/FutureDemo
user18853

107

Se si utilizza Java 8, è possibile farlo più facilmente con CompletableFuture e CompletableFuture.allOf , che applica il callback solo dopo aver completato tutti i CompletableFutures forniti.

// 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
Ciao @Andrejs, potresti spiegare cosa fa questo frammento di codice. Vedo questo suggerito in più punti, ma sono confuso su ciò che sta realmente accadendo. Come vengono gestite le eccezioni se uno dei thread fallisce?
VSEWHGHP,

2
@VSEWHGHP Da javadoc: se uno qualsiasi dei CompletableFuture forniti viene completato in modo eccezionale, anche il CompletableFuture restituito lo fa, con CompletionException che mantiene questa eccezione come causa.
Andrejs,

1
Bene, quindi stavo seguendo questo, c'è un modo per usare questo frammento ma ottenere i valori per tutti gli altri thread che sono stati completati correttamente? Devo solo scorrere l'elenco di CompletableFutures e chiamare per ignorare CompletableFuture <Elenco <T>> poiché la funzione di sequenza si occupa di garantire che tutti i thread siano completi con risultato o eccezione?
VSEWHGHP,

6
Questo sta risolvendo un problema diverso. Se hai Futureistanze, non puoi applicare questo metodo. Non è facile convertirlo Futurein CompletableFuture.
Jarekczek,

non funzionerà se abbiamo qualche eccezione in qualche compito.
slisnychyi,

21

Usa a CompletableFuturein 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
Questa dovrebbe essere la risposta accettata. Fa
maaw

Funziona come previsto.
Dimon

15

È possibile utilizzare un ExecutorCompletionService . La documentazione ha anche un esempio per il tuo caso d'uso esatto:

Supponi invece che desideri utilizzare il primo risultato non nullo dell'insieme di attività, ignorando quelle che incontrano eccezioni e annullando tutte le altre attività quando la prima è pronta:

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 cosa importante da notare qui è che ecs.take () otterrà la prima attività completata , non solo la prima inviata. Quindi dovresti ottenerli nell'ordine di finire l'esecuzione (o lanciare un'eccezione).


3

Se stai usando Java 8 e non vuoi manipolare CompletableFutures, ho scritto uno strumento per recuperare i risultati per uno List<Future<T>>streaming usando. La chiave è che ti è proibito map(Future::get)mentre getta.

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

Questo ha bisogno di uno AggregateExceptionche funzioni come 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;
    }

}

Questo componente funziona esattamente come Task.WaitAll di C # . Sto lavorando a una variante che fa lo stesso di CompletableFuture.allOf(equivalente a Task.WhenAll)

Il motivo per cui l'ho fatto è che sto usando Spring's ListenableFuturee non voglio CompletableFutureportarlo nonostante sia un modo più standard


1
Valuta per aver visto la necessità di una AggregateException equivalente.
granadaCoder

Un esempio di utilizzo di questa funzione sarebbe carino.
XDS

1

Nel caso in cui desideri combinare un elenco di CompletableFutures, puoi farlo:

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

Per maggiori dettagli su Future & CompletableFuture, link utili:
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

forse questo sarebbe d'aiuto (niente verrebbe sostituito con un thread non elaborato, sì!) Suggerisco di eseguire ogni Futuretipo con un thread separato (vanno in parallelo), quindi quando mai uno degli errori ha ottenuto, segnala solo il manager ( 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...");}
}
}

Devo dire che il codice sopra sarebbe errore (non controllato), ma spero di poter spiegare la soluzione. per favore, prova.


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

CompletionService prenderà i tuoi Callables con il metodo .submit () e potrai recuperare i futures calcolati con il metodo .take ().

Una cosa che non devi dimenticare è di terminare ExecutorService chiamando il metodo .shutdown (). Inoltre, puoi chiamare questo metodo solo quando hai salvato un riferimento al servizio di esecuzione, quindi assicurati di conservarne uno.

Codice di esempio - Per un numero fisso di elementi di lavoro su cui lavorare in parallelo:

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

Codice di esempio - Per un numero dinamico di elementi di lavoro su cui lavorare in parallelo:

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

Ho una classe di utilità che contiene questi:

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

Una volta che lo hai fatto, usando un'importazione statica, puoi semplicemente aspettare tutti i futuri come questo:

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

puoi anche raccogliere tutti i loro risultati in questo modo:

List<MyResultType> results = futures.stream()
    .map(future -> uncheckedSupplier(future::get).get())
    .collect(Collectors.toList());

Sto solo rivisitando il mio vecchio post e notando che hai avuto un altro dolore:

Ma il problema qui è se, ad esempio, il 4 ° futuro genera un'eccezione, quindi aspetterò inutilmente che i primi 3 futuri siano disponibili.

In questo caso, la soluzione semplice è fare questo in parallelo:

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

In questo modo la prima eccezione, sebbene non fermerà il futuro, interromperà l'istruzione forEach, come nell'esempio seriale, ma poiché tutti attendono in parallelo, non dovrete attendere il completamento dei primi 3.


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

    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.