Каков наилучший способ обработки ExecutionException?

У меня есть метод, который выполняет некоторую задачу с таймаутом. Я использую ExecutorServer.submit() чтобы получить будущий объект, а затем я вызываю 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 );
        }
    }
}

= = = UPDATE ===

многие люди опубликовали ответы, которые рекомендовали либо 1) повторное выбрасывание как общее исключение, либо 2) повторное выбрасывание как непроверенное исключение. Я не хочу делать ни то, ни другое, потому что эти типы исключений (ProcessExecutionException, InterruptedException, IOException, TimeoutException) важны - они будут каждый обрабатывается по-разному обработанным вызовом. Если бы мне не нужна была функция тайм-аута, я бы хотел, чтобы мой метод выбросил эти 4 конкретных типа исключений (ну, за исключением TimeoutException). Я не думаю, что добавление функции тайм-аута должно изменить мою подпись метода, чтобы вызвать общий тип исключения.

10 ответов


я подробно рассмотрел эту проблему, и это беспорядок. Нет простого ответа ни в Java 5, ни в 6 или 7. В дополнение к неуклюжести, многословию и хрупкости, которые вы указываете, ваше решение на самом деле имеет проблему, что ExecutionException что вы раздеваетесь, когда звоните getCause() на самом деле содержит большую часть важной информации трассировки стека!

то есть вся информация стека потока, выполняющего метод в представленном коде, находится только в ExcecutionException, а не во вложенных причинах, которые охватывают только фреймы, начинающиеся с call() в функцию. То есть ваш doSomethingWithTimeout метод даже не появится в трассировках стека исключений, которые вы бросаете здесь! Вы получите только бестелесный стек от исполнителя. Это потому что ExecutionException - единственный, который был создан в вызывающем потоке (см. FutureTask.get()).

единственное решение, которое я знаю-это сложно. Большая часть проблем связана с либералами. спецификация исключений Callable - throws Exception. Вы можете определить новые варианты Callable которые точно указывают, какие исключения они бросают, такие как:

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

    @Override
    T call() throws X; 
}

это позволяет методам, которые выполняют вызываемые объекты, иметь более точный throws предложения. Если вы хотите поддерживать подписи с до n исключениями, вам понадобится N вариантов этого интерфейса, к сожалению.

теперь вы можете написать обертку вокруг JDK Executor который принимает увеличенный вызываемый, и возвращает расширенный Future, что-то вроде гуавы по CheckedFuture. Проверенные типы исключений распространяются во время компиляции из создания и типа ExecutorService, возвращенный Futures, и в конечном итоге на getChecked метод на будущее.

вот как вы проходите безопасность типа времени компиляции. Это означает, что вместо вызова:

Future.get() throws InterruptedException, ExecutionException;

вы можете по телефонам:

CheckedFuture.getChecked() throws InterruptedException, ProcessExecutionException, IOException

таким образом, проблема разворачивания избегается - ваш метод немедленно выдает исключения требуемого типа, и они доступны и проверяются во время компиляции.

внутри , вы еще необходимо решить проблему" отсутствующей причины", описанную выше. Это можно сделать, сшив текущий стек (вызывающего потока) в стек создаваемого исключения. Это растяжение обычного использования трассировки стека в Java, так как один стек растягивается по потокам, но он работает и это легко понять, как только вы знаете, что происходит.

другой вариант-создать другое исключение того же, что и выбрасываемый, и установить оригинал в качестве причины нового. Вы получите полную трассировку стека, и связь причин будет такой же, как и при работе с ExecutionException - но у вас будет правильный тип исключения. Однако вам нужно будет использовать отражение и не гарантируется работа, например, для объектов без конструктора обычные параметры.


вот пара интересной информации для проверенных и против проверенных исключений. Брайан Гетц обсуждения и аргумент против проверенных исключений из Эккель Обсуждения. Но я не знал, если вы уже реализовали и дали мысль о проверенном рефакторе исключений, который обсуждается Джошуа в этом книги.

согласно эффективному Java pearls, один из предпочтительных методов обработки проверен исключение состоит в том, чтобы превратить проверенное исключение в непроверенное исключение. Так, например,

try{
obj.someAction()
}catch(CheckedException excep){
}

измените эту реализацию на

if(obj.canThisOperationBeperformed){
obj.someAction()
}else{
// Handle the required Exception.
}

Я боюсь, что нет ответа на вашу проблему. В принципе, вы запускаете задачу в другом потоке, чем тот, в котором вы находитесь, и хотите использовать шаблон ExecutorService, чтобы поймать все исключения, которые может бросить задача, плюс бонус прерывания этой задачи через определенное время. Ваш подход правильный : вы не могли бы сделать это с голым Runnable.

и это исключение, о котором у вас нет информации, вы хотите бросить его снова, с определенным тип: ProcessExecutionException, InterruptedException или IOException. Если это другой тип, вы хотите перестроить его как исключение RuntimeException (что, кстати, не лучшее решение, так как вы не охватываете все случаи).

таким образом, у вас есть несоответствие impendance : Throwable с одной стороны, и известный тип исключения с другой. Единственное решение, которое вам нужно решить, - это сделать то, что вы сделали : проверить тип и бросить его снова с приведением. Можно написать по-другому, но будет выглядеть то же самое в конце...


вот что я делаю в этой ситуации. Этим достигается следующее:

  • повторные броски проверенных исключений без их упаковки
  • склеивает следы стека

код:

public <V> V waitForThingToComplete(Future<V> future) {
    boolean interrupted = false;
    try {
        while (true) {
            try {
                return future.get();
            } catch (InterruptedException e) {
                interrupted = true;
            }
        }
    } catch (ExecutionException e) {
        final Throwable cause = e.getCause();
        this.prependCurrentStackTrace(cause);
        throw this.<RuntimeException>maskException(cause);
    } catch (CancellationException e) {
        throw new RuntimeException("operation was canceled", e);
    } finally {
        if (interrupted)
            Thread.currentThread().interrupt();
    }
}

// Prepend stack frames from the current thread onto exception trace
private void prependCurrentStackTrace(Throwable t) {
    final StackTraceElement[] innerFrames = t.getStackTrace();
    final StackTraceElement[] outerFrames = new Throwable().getStackTrace();
    final StackTraceElement[] frames = new StackTraceElement[innerFrames.length + outerFrames.length];
    System.arraycopy(innerFrames, 0, frames, 0, innerFrames.length);
    frames[innerFrames.length] = new StackTraceElement(this.getClass().getName(),
      "<placeholder>", "Changed Threads", -1);
    for (int i = 1; i < outerFrames.length; i++)
        frames[innerFrames.length + i] = outerFrames[i];
    t.setStackTrace(frames);
}

// Checked exception masker
@SuppressWarnings("unchecked")
private <T extends Throwable> T maskException(Throwable t) throws T {
    throw (T)t;
}

Кажется, работает.


Я не уверен, почему у вас есть блок if/else в улове и instanceof, Я думаю, вы можете делать то, что хотите с: -

catch( ProcessExecutionException ex )
{
   // handle ProcessExecutionException
}
catch( InterruptException ex )
{
   // handler InterruptException*
}

одна вещь, которую нужно учитывать, чтобы уменьшить беспорядок,-это поймать исключение внутри вызываемого метода и повторно бросить как собственное исключение или исключения для домена/пакета. Сколько исключений вам нужно создать, в значительной степени зависит от того, как ваш вызывающий код ответит на исключение.


javadoc java.util.concurrent.Future.get() указано следующее. Тогда почему бы просто не поймать ExecutionException (и отмена и прерывание, как объявлено java.util.concurrent.Future.get()) метод?

...
Броски:

CancellationException-если вычисление было отменено

ExecutionException-если вычисление вызвало исключение

InterruptedException - если текущий поток был прерван пока ожидание

так что в основном вы можете бросить любое исключение в пределах вашего вызываемого и просто поймать ExecutionException. Тогда ExecutionException.getCause() будет содержать фактическое исключение, которое ваш вызываемый бросил, как указано в javadoc. Таким образом, вы защищены от изменений подписи метода, связанных с проверенным объявлением исключения.

кстати, вы никогда не должны поймать Throwable, как это поймать и RuntimeExceptions и Errors. Ловить Exception немного лучше, но все равно не рекомендуется, так как он будет ловить RuntimeExceptions.

что-то типа:

               try {

                    MyResult result = myFutureTask.get();
                } catch (ExecutionException e) {
                    if (errorHandler != null) {
                        errorHandler.handleExecutionException(e);
                    }
                    logger.error(e);
                } catch (CancellationException e) {
                    if (errorHandler != null) {
                        errorHandler.handleCancelationException(e);
                    }
                    logger.error(e);

                } catch (InterruptedException e) {
                    if (errorHandler != null) {
                        errorHandler.handleInterruptedException(e);
                    }
                    logger.error(e);
                }

в вызывающем классе поймайте Throwable последние. Например,

try{
    doSomethingWithTimeout(i);
}
catch(InterruptedException e){
    // do something
}
catch(IOException e){
    // do something
} 
catch(TimeoutException e){
    // do something
}
catch(ExecutionException e){
    // do something
}
catch(Throwable t){
    // do something
}

и содержание doSomethingWithTimeout(int timeout) должно выглядеть так,

.
.
.
ExecutorService service = Executors.newSingleThreadExecutor();
try {
    Future<byte[]> future = service.submit( callable );
    return future.get( timeout, TimeUnit.MILLISECONDS );
} 
catch(Throwable t){
    throw t;
}
finally{
    service.shutdown();
}

и это подпись метода должна выглядеть так:

doSomethingWithTimeout(int timeout) throws Throwable


вот еще один способ сделать это, хотя я не уверен, что это менее неуклюже или менее склонно к разрыву, чем делать это с помощью instanceof check, как в вашем вопросе:

public static byte[] doSomethingWithTimeout(int timeout)
        throws ProcessExecutionException, InterruptedException, IOException, TimeoutException {
    ....
    try {
        ....
        return future.get(1000, TimeUnit.MILLISECONDS);
        .....
    } catch (ExecutionException e) {

        try {
            throw e.getCause();
        } catch (IOException ioe) {
            throw ioe;
        } catch (InterruptedException ie) {
            throw ie;
        } catch (ProcessExecutionException pee) {
            throw pee;
        } catch (Throwable t) {
            //Unhandled exception from Callable endups here
        }

    } catch (TimeoutException e) {
        throw e;
    } catch.....
}

Я бы не сказал, что рекомендую это, но вот как вы можете это сделать. Он безопасен для типов, и тот, кто придет, чтобы изменить его после вас, вероятно, будет недоволен им.

public class ConsumerClass {

    public static byte[] doSomethingWithTimeout(int timeout)
            throws ProcessExecutionException, InterruptedException, IOException, TimeoutException {
        MyCallable callable = new MyCallable();
        ExecutorService service = Executors.newSingleThreadExecutor();
        try {
            Future<byte[]> future = service.submit(callable);
            return future.get(timeout, TimeUnit.MILLISECONDS);
        } catch (ExecutionException e) {
            throw callable.rethrow(e);
        } finally {
            service.shutdown();
        }
    }

}

// Need to subclass this new callable type to provide the Exception classes.
// This is where users of your API have to pay the price for type-safety.
public class MyCallable extends CallableWithExceptions<byte[], ProcessExecutionException, IOException> {

    public MyCallable() {
        super(ProcessExecutionException.class, IOException.class);
    }

    @Override
    public byte[] call() throws ProcessExecutionException, IOException {
        //Do some work that could throw one of these exceptions
        return null;
    }

}

// This is the generic implementation. You will need to do some more work
// if you want it to support a number of exception types other than two.
public abstract class CallableWithExceptions<V, E1 extends Exception, E2 extends Exception>
        implements Callable<V> {

    private Class<E1> e1;
    private Class<E2> e2;

    public CallableWithExceptions(Class<E1> e1, Class<E2> e2) {
        this.e1 = e1;
        this.e2 = e2;
    }

    public abstract V call() throws E1, E2;

    // This method always throws, but calling code can throw the result
    // from this method to avoid compiler errors.
    public RuntimeException rethrow(ExecutionException ee) throws E1, E2 {
        Throwable t = ee.getCause();

        if (e1.isInstance(t)) {
            throw e1.cast(t);
        } else if (e2.isInstance(t)) {
            throw e2.cast(t);
        } else if (t instanceof Error ) {
            throw (Error) t;
        } else if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new RuntimeException(t);
        }
    }

}

Я нашел один способ решить проблему. Если это ExecutionException, вы можете получить оригинальный, вызвав исключение.getCause() Затем вам нужно обернуть свое исключение в какое-то исключение времени выполнения или (что для меня лучше всего) использовать аннотацию @SneakyThrows из project lombok (https://projectlombok.org/). Я приведу небольшой пример кода. Кроме того, вы можете добавить несколько проверок instanceof перед созданием исключения, чтобы убедиться, что это тот, который вы ожидающий.

@SneakyThrows
public <T> T submitAndGet(Callable<T> task) {
    try {
        return executor.submit(task).get(5, TimeUnit.SECONDS);
    } catch (InterruptedException | ExecutionException | TimeoutException e) {
        throw e.getCause();
    }
}