У меня возникла проблема при преобразовании моего кода в неблокирующий код с использованием CompletableFuture. Чтобы минимизировать суть вопроса, я создал пример кода , который ведет себя по-разному, когда я использую CompletableFuture. Проблема в том, что CompletableFuture проглатывает исключение из Runnable-Delegation.
Я использую делегирование поверх Runnable и ExecutorService для предоставления некоторого кода оболочки, необходимого в моем исходном приложении.
Пример кода:
MyRunnable: мой исполняемый образец, который всегда выдает исключение.
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 - это делегирование runnable, который делегирует и переносит логику вокруг Runnable, переданного ему, и заполнитель для обработки исключений.
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 - этот метод делегатов выполняет. Он просто оборачивает работоспособный объект с 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 - я использую два подхода. Way1 - использование ExecutorService без CompletableFuture. Way2 - использование 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());
}
}
Вопрос:
Когда я запускаю way1 (), вывод
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
Вы можете заметить, что блок catch объекта DelegatingRunnable может перехватывать здесь исключение, которое возникает из MyRunnable. Но если я использую way2 () с использованием CompletableFuture, исключение из MyRunnable не попадает под DelegatingRunnable, хотя я вижу, что это кашель при обратном вызове whenComplete CompletableFuture.
Выход 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
Вы можете заметить, что CompletableFuture использует один и тот же DelegatingExecutionService и DelegatingRunnable для внутреннего использования. Я не понимаю, почему DelegatingRunnable не может поймать исключение в этом случае.
(Почему я использую CompletableFuture? - это всего лишь пример кода, чтобы объяснить точную проблему, с которой я сталкиваюсь. Но в целом мне нужно использовать CompletableFuture, чтобы сделать цепочку задач виртуально неблокирующим способом)