Esperando em uma lista de Futuro


145

Eu tenho um método que retorna um Listdos futuros

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

Agora, quero esperar até que todos os futuros sejam processados ​​com êxito ou que qualquer uma das tarefas cuja saída seja retornada por um futuro gere uma exceção. Mesmo que uma tarefa gere uma exceção, não faz sentido esperar pelos outros futuros.

Uma abordagem simples seria

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

Mas o problema aqui é se, por exemplo, o quarto futuro lançar uma exceção, esperarei desnecessariamente os 3 primeiros futuros disponíveis.

Como resolver isso? A contagem decrescente trava a ajuda de alguma maneira? Não consigo usar o Future isDoneporque o documento java diz

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
quem gera esses futuros? De que tipo eles são? A interface java.util.concurrent.Future não fornece a funcionalidade que você deseja, a única maneira é usar seu próprio futuro com retornos de chamada.
Alexei Kaigorodov 13/10/2013

Você pode criar uma instância ExecutionServicepara cada "lote" de tarefas, enviá-las a ele e, em seguida, desligar imediatamente o serviço e usá awaitTermination()-lo, suponho.
Millimoose #

Você pode usar a CountDownLatchse você envolveu o corpo de todos os seus futuros em um try..finallypara garantir que a trava também diminua.
Millimoose #


@AlexeiKaigorodov Sim, meu futuro são de java.util.concurrent.I tipo estou processando futuro com callable.I get Futture quando eu enviar uma tarefa a um execureservice
user93796

Respostas:


124

Você pode usar um CompletionService para receber os futuros assim que estiverem prontos e, se um deles lançar uma exceção, cancele o processamento. Algo assim:

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

Eu acho que você pode melhorar ainda mais para cancelar as tarefas ainda em execução, se uma delas gerar um erro.


1
: Seu código tem o mesmo problema que eu mencionei no meu post. Se o futuro lançar uma exceção, o código ainda aguardará o 1,2,3 futuro. ou o completeSerice.take) retornará o futuro que terminar primeiro?
user93796

1
Posso dizer ao serviço de conclusão que aguarde X segundos no máximo?
user93796

1
Não deveria ter. Ele não repete os futuros, mas assim que um está pronto, é processado / verificado se não for lançada a exceção.
Dcernahoschi 13/10

2
Para atingir o tempo limite de espera para o futuro aparecer na fila, existe um método de votação (segundos) no CompletionService.
Dcernahoschi 13/10

Aqui está o exemplo de trabalho no github: github.com/princegoyal1987/FutureDemo
user18853

107

Se você estiver usando o Java 8 , poderá fazer isso mais facilmente com CompletableFuture e CompletableFuture.allOf , que aplica o retorno de chamada somente depois que todas as CompletableFutures fornecidas forem concluídas.

// 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
Olá @Andrejs, você poderia explicar o que esse trecho de código faz. Eu vejo isso sugerido em vários lugares, mas estou confuso sobre o que realmente está acontecendo. Como são tratadas as exceções se um dos threads falhar?
VSEWHGHP 14/01

2
@VSEWHGHP No javadoc: se algum dos CompletableFutures fornecido for concluído excepcionalmente, o CompletableFuture retornado também o fará, com um CompletionException mantendo essa exceção como causa.
Andrejs

1
Certo, então eu estava acompanhando isso, existe alguma maneira de usar esse snippet, mas obter os valores para todos os outros threads que foram concluídos com êxito? Devo apenas iterar na lista CompletableFutures e chamar ignorar o CompletableFuture <List <T>>, pois a função de sequência cuida de garantir que todos os threads sejam concluídos com resultado ou exceção?
VSEWHGHP 14/01

6
Isso está resolvendo um problema diferente. Se você tiver Futureinstâncias, não poderá aplicar esse método. Não é fácil converter Futureem CompletableFuture.
Jarekczek

não funcionará se tivermos exceção em alguma tarefa.
slisnychyi 6/08/19

21

Use a CompletableFutureem 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
Essa deve ser a resposta aceita. Também faz parte da documentação oficial do Spring: spring.io/guides/gs/async-method
maaw

Funciona como esperado.
Dimon

15

Você pode usar um ExecutorCompletionService . A documentação ainda tem um exemplo para o seu caso de uso exato:

Suponha, em vez disso, que você gostaria de usar o primeiro resultado não nulo do conjunto de tarefas, ignorando os que encontrarem exceções e cancelando todas as outras tarefas quando a primeira estiver 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);
}

O importante a ser observado aqui é que ecs.take () obterá a primeira tarefa concluída , não apenas a primeira enviada. Portanto, você deve obtê-los na ordem de concluir a execução (ou lançar uma exceção).


3

Se você estiver usando o Java 8 e não quiser manipular CompletableFutures, escrevi uma ferramenta para recuperar resultados para um List<Future<T>>streaming em uso. A chave é que você está proibido map(Future::get)enquanto joga.

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

Isso precisa de um AggregateExceptionque funcione como o 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;
    }

}

Esse componente atua exatamente como Task.WaitAll do C # . Estou trabalhando em uma variante que faz o mesmo que CompletableFuture.allOf(equivalente a Task.WhenAll)

A razão pela qual fiz isso é que estou usando o Spring ListenableFuturee não quero portar, CompletableFutureapesar de ser uma maneira mais padrão


1
Voto positivo por ver a necessidade de uma AggregateException equivalente.
usar o seguinte código

Um exemplo de uso desse recurso seria bom.
XDS

1

Caso deseje combinar uma Lista de CompletableFutures, você pode fazer isso:

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

Para obter mais detalhes sobre o Future & CompletableFuture, links úteis:
1. Futuro: 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

Talvez isso ajude (nada seria substituído pelo thread bruto, sim!). Sugiro que execute cada Futureindivíduo com um thread separado (eles ficam paralelos); então, sempre que um dos erros ocorrer , basta sinalizar para o gerente ( 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 dizer que o código acima apresentaria um erro (não foi verificado), mas espero poder explicar a solução. por favor, tente.


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

O CompletionService utilizará seus Callables com o método .submit () e você poderá recuperar os futuros calculados com o método .take ().

Uma coisa que você não deve esquecer é encerrar o ExecutorService chamando o método .shutdown (). Além disso, você só pode chamar esse método quando tiver salvo uma referência ao serviço do executor, portanto, mantenha-o.

Código de exemplo - Para um número fixo de itens de trabalho a serem trabalhados em paralelo:

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

Código de exemplo - Para um número dinâmico de itens de trabalho a serem trabalhados em paralelo:

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

Eu tenho uma classe de utilitário que contém estes:

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

Depois disso, usando uma importação estática, você pode simplesmente esperar por todos os futuros como este:

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

você também pode coletar todos os resultados assim:

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

Apenas revisitando meu post antigo e percebendo que você teve outra tristeza:

Mas o problema aqui é se, por exemplo, o quarto futuro lançar uma exceção, esperarei desnecessariamente os 3 primeiros futuros disponíveis.

Nesse caso, a solução simples é fazer isso em paralelo:

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

Dessa forma, a primeira exceção, embora não pare o futuro, interromperá a instrução forEach, como no exemplo de série, mas como todos esperam em paralelo, não será necessário aguardar a conclusão dos três primeiros.


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

    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.