处理ExecutionException的最佳方法是什么?

Jes*_*num 38 java future executorservice executionexception

我有一个方法,通过超时执行一些任务.我使用ExecutorServer.submit()来获取Future对象,然后使用超时调用future.get().这工作正常,但我的问题是处理我的任务可以抛出的已检查异常的最佳方法.以下代码有效,并保留已检查的异常,但如果方法签名中的已检查异常列表发生更改,则它似乎非常笨拙且容易中断.

对于如何解决这个问题,有任何的建议吗?我需要针对Java 5,但我也很想知道在较新版本的Java中是否有好的解决方案.

public static byte[] doSomethingWithTimeout( int timeout ) throws ProcessExecutionException, InterruptedException, IOException, TimeoutException {

    Callable<byte[]> callable = new Callable<byte[]>() {
        public byte[] call() throws IOException, InterruptedException, ProcessExecutionException {
            //Do some work that could throw one of these exceptions
            return null;
        }
    };

    try {
        ExecutorService service = Executors.newSingleThreadExecutor();
        try {
            Future<byte[]> future = service.submit( callable );
            return future.get( timeout, TimeUnit.MILLISECONDS );
        } finally {
            service.shutdown();
        }
    } catch( Throwable t ) { //Exception handling of nested exceptions is painfully clumsy in Java
        if( t instanceof ExecutionException ) {
            t = t.getCause();
        }
        if( t instanceof ProcessExecutionException ) {
            throw (ProcessExecutionException)t;
        } else if( t instanceof InterruptedException ) {
            throw (InterruptedException)t;
        } else if( t instanceof IOException ) {
            throw (IOException)t;
        } else if( t instanceof TimeoutException ) {
            throw (TimeoutException)t;
        } else if( t instanceof Error ) {
            throw (Error)t;
        } else if( t instanceof RuntimeException) {
            throw (RuntimeException)t;
        } else {
            throw new RuntimeException( t );
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

===更新===

许多人发布的回复建议:1)重新投掷作为一般例外,或2)作为未经检查的例外重新投掷.我不想做其中任何一个,因为这些异常类型(ProcessExecutionException,InterruptedException,IOException,TimeoutException)很重要 - 它们将由处理的调用以不同方式处理.如果我不需要超时功能,那么我希望我的方法抛出这4种特定的异常类型(好吧,除了TimeoutException).我不认为添加超时功能应该更改我的方法签名以抛出一般的异常类型.

Bee*_*ope 19

我深入研究了这个问题,这是一个烂摊子.在Java 5中没有简单的答案,在6或7中也没有简单的答案.除了你指出的笨拙,冗长和脆弱之外,你的解决方案实际上还有一个问题,ExecutionException即当你打电话时你正在剥离getCause()它的大部分内容重要的堆栈跟踪信息!

也就是说,在您呈现的代码中执行方法的线程的所有堆栈信息仅在ExcecutionException中,而不在嵌套原因中,而这些原因仅覆盖从call()Callable 开始的帧.也就是说,您的doSomethingWithTimeout方法甚至不会出现在您抛出的异常的堆栈跟踪中!你只能从执行者那里得到无实体的堆栈.这是因为它ExecutionException是在调用线程上创建的唯一一个(请参阅参考资料FutureTask.get()).

我所知道的唯一解决方案很复杂.很多问题的起源与自由派异常规范Callable- throws Exception.您可以定义新变体,Callable准确指定它们抛出的异常,例如:

public interface Callable1<T,X extends Exception> extends Callable<T> {

    @Override
    T call() throws X; 
}
Run Code Online (Sandbox Code Playgroud)

这允许执行callables的方法具有更精确的throws子句.如果您想支持最多N个例外的签名,遗憾的是,您将需要此接口的N个变体.

现在你可以在JDK周围编写一个包装器来Executor获取增强的Callable,然后返回一个Future像guava的CheckedFuture这样的增强.已检查的异常类型在编译时从创建和类型传播ExecutorService到返回的Futures,并最终getChecked在未来的方法上传播.

这就是你通过编译时类型安全的方式.这意味着而不是调用:

Future.get() throws InterruptedException, ExecutionException;
Run Code Online (Sandbox Code Playgroud)

你可以打电话:

CheckedFuture.getChecked() throws InterruptedException, ProcessExecutionException, IOException
Run Code Online (Sandbox Code Playgroud)

因此,避免了解包问题 - 您的方法立即抛出所需类型的异常,并且它们在编译时可用并检查.

在内部getChecked,你仍然需要解决上面描述的"缺失原因"展开问题.您可以通过将当前堆栈(调用线程)拼接到抛出异常的堆栈上来完成此操作.这延伸了Java中堆栈跟踪的常规用法,因为单个堆栈跨越线程,但是一旦你知道发生了什么,它就可以工作并且很容易理解.

另一种选择是创建另一个同样的事情作为一个被抛出的异常,并设定原为新的事业.您将获得完整的堆栈跟踪,并且原因关系将与其工作方式相同ExecutionException- 但您将拥有正确的异常类型.但是,您需要使用反射,并且不能保证工作,例如,对于没有具有通常参数的构造函数的对象.