使用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来以非阻塞方式紧急地建立任务链)
答案 0 :(得分:5)
在CompletableFuture
的源代码中,您可以看到它将给定的Runnable
包装在类型为AsyncRun
的对象中,该对象本身实现了Runnable
。
该AsyncRun
将被传递给执行者的execute
方法。
当内部/原始Runnable
引发异常时,它会被AsyncRun
的代码捕获,并且CompletableFuture
会以失败的身份完成,但异常 not 不会得到重新抛出。
这就是为什么包装器(DelegatingRunnable
)永远不会看到异常的原因。