Captura de exceções de encadeamento do Java ExecutorService

Eu estou trabalhando em uma estrutura de desenvolvimento de software para computação paralela JavaSeis.org . Eu preciso de um mecanismo robusto para relatar exceções de thread. Durante o desenvolvimento, saber de onde as exceções vieram tem um valor alto, então eu gostaria de errar do lado do excesso de relatórios. Eu também gostaria de poder manipular o teste Junit4 em threads também. A abordagem abaixo é razoável ou existe uma maneira melhor?

import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class TestThreadFailure { public static void main(String[] args) { int size = 1; ExecutorService exec = Executors.newFixedThreadPool(size); ThreadFailTask worker = new ThreadFailTask(); Future result = exec.submit(worker); try { Integer value = result.get(); System.out.println("Result: " + value); } catch (Throwable t) { System.out.println("Caught failure: " + t.toString()); exec.shutdownNow(); System.out.println("Stack Trace:"); t.printStackTrace(); return; } throw new RuntimeException("Did not catch failure !!"); } public static class ThreadFailTask implements Callable { @Override public Integer call() { int nbuf = 65536; double[][] buf = new double[nbuf][nbuf]; return new Integer((int) buf[0][0]); } } } 

Eu não acredito que haja um ‘gancho’ padrão para chegar a essas exceções ao usar submit() . No entanto, se você precisar oferecer suporte a submit() (o que parece razoável, considerando que você usa um Callable ), você sempre pode agrupar os Callables e Runnables:

 ExecutorService executor = new ThreadPoolExecutor(1, 10, 60, TimeUnit.SECONDS, new LinkedBlockingDeque()) { @Override public  Future submit(final Callable task) { Callable wrappedTask = new Callable() { @Override public T call() throws Exception { try { return task.call(); } catch (Exception e) { System.out.println("Oh boy, something broke!"); e.printStackTrace(); throw e; } } }; return super.submit(wrappedTask); } }; 

Claro, esse método só funciona se você for o único a construir o ExecutorService em primeiro lugar. Além disso, lembre-se de replace todas as três variantes submit() .

Considere chamar execute() vez de submit() no ExecutorService . Um Thread invocado com execute() invocará o Thread.UncaughtExceptionHandler quando falhar.

Basta criar um ThreadFactory que instale um Thread.UncaughtExceptionHandler em todos os Threads e invoque seu trabalho com execute() no ExecutorService vez de submit() .

Dê uma olhada nesta questão de estouro de pilha relacionada .

Conforme explicado neste tópico Qual é a diferença entre o método submit e execute com o ThreadPoolExecutor , o uso da execução só funcionará se você implementar o Runnable e não o Callable como o executável não pode retornar um futuro.

Eu acho que no seu cenário você deve construir o object futuro para que ele também possa acomodar as exceções. Então, em caso de exceção, você constrói o object da mensagem de erro.

Minha pergunta original perguntou como implementar exception handling de thread “robusta” com Java ExecutorService. Agradecemos a Angelo e Greg pelos pointers sobre como o tratamento de exceções funciona com ExecutorService.submit () e Future.get (). Meu fragment de código revisado é mostrado abaixo. O ponto chave que aprendi aqui é que Future.get () captura todas as exceções. Se o thread foi interrompido ou cancelado, você obtém a exceção apropriada, caso contrário, a exceção é quebrada e lançada novamente como uma ExecutionException.

 import java.util.concurrent.Callable;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;

 class pública TestThreadFailure {

   public static void main (String [] args) {
     int size = 1;
     ExecutorService exec = Executors.newFixedThreadPool (size);
     ThreadFailTask ​​worker = new ThreadFailTask ​​();
     Resultado futuro = exec.submit (worker);
     experimentar {
       Valor inteiro = result.get ();
       System.out.println ("Resultado:" + valor);
     } catch (ExecutionException ex) {
       System.out.println ("Falha de captura:" + ex.toString ());
       exec.shutdownNow ();
       Retorna;
     } catch (InterruptedException iex) {
       System.out.println ("Thread interrompido:" + iex.toString ());
     } catch (CancellationException cex) {
       System.out.println ("Thread cancelado:" + cex.toString ());
     }
     exec.shutdownNow ();
     throw new RuntimeException ("Não detectou falha !!");
   }

   public static class ThreadFailTask ​​implementa Callable {
     @Sobrepor
     public Integer call () {
       int nbuf = 65536;
       double [] [] buf = novo duplo [nbuf] [nbuf];
       return new Inteiro ((int) buf [0] [0]);
     }
   }
 }

Eu não tive muita sorte com outras respostas porque precisei da própria instância de exceção, não apenas de um rastreamento de pilha impresso. Para mim, a resposta aceita envolvendo ThreadPoolExecutor # afterExecute () da pergunta ” Por que o UncaughtExceptionHandler não foi chamado pelo ExecutorService? ” Funcionou.

Veja o seguinte código de exemplo:

 List tasks = new LinkedList<>(); for (int i = 0; i < numThreads; ++i) { Runnable task = new Runnable() { @Override public void run() { throw new RuntimeException(); } }; tasks.add(task); } Optional opEmpty = Optional.empty(); /* * Use AtomicReference as a means of capturing the first thrown exception, since a * spawned thread can't "throw" an exception to the parent thread. */ final AtomicReference> firstThrownException = new AtomicReference<>(opEmpty); /* * Use new ThreadPoolExecutor instead of Executors.newFixedThreadPool() so * that I can override afterExecute() for the purposes of throwing an * exception from the test thread if a child thread fails. */ ExecutorService execSvc = new ThreadPoolExecutor(numThreads, numThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()) { @Override public void afterExecute(Runnable task, Throwable failureCause) { if(failureCause == null) { // The Runnable completed successfully. return; } // only sets the first exception because it will only be empty on the first call. firstThrownException.compareAndSet(Optional.empty(), Optional.of(failureCause)); } }; for (Runnable task : tasks) { execSvc.execute(task); } execSvc.shutdown(); execSvc.awaitTermination(1, TimeUnit.HOURS); assertEquals(firstThrownException.get(), Optional.empty()); 

Para lidar com exceções no ExecutorService você tem que aproveitar as vantagens de Callable e Future .

Por favor, assista ao vídeo abaixo para mais detalhes. Espero que isso ajude você.

VIDEO: Callable e Futuro (11 min)