Estou enfrentando um problema ao converter meu código para non-blocking código usando CompletableFuture. Para minimizar o alcance da questão, eu criei um código de exemplo que se comporta de forma diferente quando eu uso CompletableFuture. A questão é CompletableFuture engole a exceção de Runnable-delegação.
Eu estou usando delegação no topo de Runnable e ExecutorService para fornecer algum código invólucro necessário no meu aplicativo original.
Código de amostra:
MyRunnable: Meu executável amostra, que joga sempre a exceção.
public class MyRunnable implements Runnable { @Override public void run() { System.out.println("This is My Thread throwing exception : " + Thread.currentThread().getName()); throw new RuntimeException("Runtime exception from MyThread"); } }
DelegatingRunnable - este é delegar executável que delegados e lógica envoltório ao redor do Runnable passado para ele, e espaço reservado para o tratamento de exceção.
public class DelegatingRunnable implements Runnable { private Runnable delegate; public DelegatingRunnable(Runnable delegate) { this.delegate = delegate; } @Override public void run() { System.out.println("Delegating Thread start : " + Thread.currentThread().getName()); try { // Some code before thread execution delegate.run(); // Some code after thread execution } catch (Exception e) { // While using CompletableFuture, could not catch exception here System.out.println("###### Delegating Thread Exception Caught : " + Thread.currentThread().getName()); //throw new RuntimeException(e.getMessage()); } catch (Throwable t) { System.out.println("!!!!!!! Delegating Thread Throwable Caught : " + Thread.currentThread().getName()); } System.out.println("Delegating Thread ends : " + Thread.currentThread().getName()); } }
DelegatingExecutorService - este delegados executar método. Ele só envolve o executável com DelegatingRunnable.
public class DelegatingExecutorService extends AbstractExecutorService { private ExecutorService executor; public DelegatingExecutorService(ExecutorService executor) { this.executor = executor; } @Override public void execute(Runnable command) { executor.execute(new DelegatingRunnable(command)); } // Othere delegating methods }
MainClass - Eu estou usando duas abordagens. WAY1 - usando ExecutorService sem CompletableFuture. Way2 - usando CompletableFuture
public class MainClass { public static void main(String[] arg) { //way1(); way2(); } public static void way2() { System.out.println("Way:2 # This is main class : " + Thread.currentThread().getName()); ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()+1); DelegatingExecutorService executorService = new DelegatingExecutorService(executor); CompletableFuture.runAsync(new MyRunnable(), executorService) .whenComplete((res, ex) -> { if (ex != null) { System.out.println("whenComplete - exception : " + Thread.currentThread().getName()); } else { System.out.println("whenComplete - success : " + Thread.currentThread().getName()); } }); executor.shutdown(); System.out.println("main class completed : " + Thread.currentThread().getName()); } public static void way1() { System.out.println("Way:1 # This is main class : " + Thread.currentThread().getName()); ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()+1); DelegatingExecutorService executorService = new DelegatingExecutorService(executor); executorService.execute(new MyRunnable()); executor.shutdown(); System.out.println("main class completed : " + Thread.currentThread().getName()); } }
Pergunta: Quando eu executo WAY1 (), a saída é
Way:1 # This is main class : main
Delegating Thread start : pool-1-thread-1
This is My Thread throwing exception : pool-1-thread-1
###### Delegating Thread Exception Caught : pool-1-thread-1
main class completed : main
Delegating Thread ends : pool-1-thread-1
Você pode notar que bloco catch de 'DelegatingRunnable' pode capturar a exceção aqui, que é levantada a partir de MyRunnable. Mas se eu usar WAY2 () usando CompletableFuture, a exceção de MyRunnable não é cought sob DelegatingRunnable, embora eu ver ele está sendo tosse sob 'whenComplete' callback de CompletableFuture.
Saída de WAY2 é
Way:2 # This is main class : main
Delegating Thread start : pool-1-thread-1
This is My Thread throwing exception : pool-1-thread-1
Delegating Thread ends : pool-1-thread-1
whenComplete - exception : main
main class completed : main
Você pode notar que o CompletableFuture está usando o mesmo DelegatingExecutionService e DelegatingRunnable internamente. Eu não entendo por que DelegatingRunnable não pode capturar a exceção neste caso.
(Por que eu estou usando CompletableFuture -? Este é apenas um código de exemplo para explicar o problema exato que eu estou enfrentando Mas no geral, eu preciso usar CompletableFuture para fazer cadeia de tarefa evantually de forma não-bloqueio).
No código-fonte de CompletableFuture
que você pode ver que ele envolve o dado Runnable
em um objeto do tipo AsyncRun
que se implementa Runnable
. Este AsyncRun
será passado para o seu executor execute
método. Quando o interior / original Runnable
gera uma exceção, ele é pego pelo código de AsyncRun
e CompletableFuture
é completado como falhou, mas a exceção será não se re-lançada.
É por isso que o seu envoltório ( DelegatingRunnable
) nunca verá a exceção.