具有Runnable-delegation的CompletableFuture-委托类中的异常被忽略

时间:2018-08-31 01:25:15

标签: java java-8 completable-future

使用CompletableFuture将代码转换为非阻塞代码时,我遇到了问题。为了最小化问题的范围,我创建了一个sample code,当我使用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包装了runnable。

    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-我正在使用两种方法。方式1-使用不带CompletableFuture的ExecutorService。 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

您会注意到'DelegatingRunnable'的catch块可以在此处捕获异常,这是从MyRunnable引发的。但是,如果我使用通过CompletableFuture使用的way2(),则在DelegatingRunnable下不会处理MyRunnable的异常,尽管我看到它在CompletableFuture的“ whenComplete”回调中正在咳嗽。

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 :(得分:5)

CompletableFuture的源代码中,您可以看到它将给定的Runnable包装在类型为AsyncRun的对象中,该对象本身实现了Runnable。 该AsyncRun将被传递给执行者的execute方法。 当内部/原始Runnable引发异常时,它会被AsyncRun的代码捕获,并且CompletableFuture会以失败的身份完成,但异常 not 不会得到重新抛出。

这就是为什么包装器(DelegatingRunnable)永远不会看到异常的原因。