CompletableFuture с Runnable-делегированием - исключение игнорируется в делегирующем классе - PullRequest
0 голосов
/ 31 августа 2018

У меня возникла проблема при преобразовании моего кода в неблокирующий код с использованием 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, чтобы сделать цепочку задач виртуально неблокирующим способом)

1 Ответ

0 голосов
/ 31 августа 2018

В исходном коде CompletableFuture вы можете видеть, что он оборачивает данный Runnable в объект типа AsyncRun, который сам реализует Runnable. Это AsyncRun будет передано методу execute вашего исполнителя. Когда внутренний / оригинальный Runnable выдает исключение, он ловится кодом AsyncRun, и CompletableFuture завершается как сбой, но исключение не будет возвращено.

Вот почему ваша оболочка (DelegatingRunnable) никогда не увидит исключение.

...