Подтвердить что ты не робот

Каков наилучший способ обработки 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 );
        }
    }
}

=== UPDATE ===

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

4b9b3361

Ответ 1

Я подробно рассмотрел эту проблему, и это беспорядок. В 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; 
}

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

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

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

Future.get() throws InterruptedException, ExecutionException;

Вы можете позвонить:

CheckedFuture.getChecked() throws InterruptedException, ProcessExecutionException, IOException

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

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

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

Ответ 2

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

Согласно Эффективному жемчугу Java, одним из предпочтительных методов обработки проверенных исключений является превращение проверенного исключения в Un-Checked Exception. Так, например,

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

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

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

Ответ 3

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

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

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

Ответ 4

Вот что я делаю в этой ситуации. Это выполняет следующее:

  • Re-throws проверяет исключения, не обертывая их
  • Склеивает трассировки стека

код:

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;
}

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

Ответ 5

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

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

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

Ответ 6

В 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);
                }

Ответ 7

В вызывающем классе перехватите Throwable last. Например,

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

Ответ 8

Я нашел один способ решить проблему. Если это ExecutionException, вы можете получить оригинальное, вызвав exception.getCause() Затем вам нужно обернуть свое исключение в какое-то исключение Runtime Exception или (что для меня лучше всего) использовать аннотацию @SneakyThrows из проекта 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();
    }
}

Ответ 9

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

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.....
}

Ответ 10

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

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);
        }
    }

}