What is the best way to handle an ExecutionException?

后端 未结 11 1676
遇见更好的自我
遇见更好的自我 2020-12-04 12:07

I have a method that performs some task with a timeout. I use the ExecutorServer.submit() to get a Future object, and then I call future.get() with a timeout. This is workin

相关标签:
11条回答
  • 2020-12-04 12:43

    Here's what I do in this situation. This accomplishes the following:

    • Re-throws checked exceptions without wrapping them
    • Glues together the stack traces

    Code:

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

    Seems to work.

    0 讨论(0)
  • 2020-12-04 12:46

    I'm afraid there's no answer to your problem. Basically, you are launching a task in a different thread than the one you are in, and want to use the ExecutorService pattern to catch all the exceptions that task can throw, plus the bonus of interrupting that task after a certain amount of time. Your approach is the right one : you couldnt do that with a bare Runnable.

    And this exception, that you have no information about, you want to throw it again, with a certain type : ProcessExecutionException, InterruptedException or IOException. If it's another type, you want to rethrow it as a RuntimeException (which is btw not the best solution, since you dont cover all the cases).

    So you have an impendance mismatch there : a Throwable on one hand, and a known exception type on the other. The only solution you have to solve it is to do what you've done : check the type, and throw it again with a cast. It can be written differently, but will look the same in the end...

    0 讨论(0)
  • 2020-12-04 12:47

    In the calling class, catch the Throwable last. For instance,

    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
    }
    

    And the content of doSomethingWithTimeout(int timeout) should look like this,

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

    And it's method signature should look like,

    doSomethingWithTimeout(int timeout) throws Throwable

    0 讨论(0)
  • 2020-12-04 12:49

    I've looked at this problem in depth, and it's a mess. There is no easy answer in Java 5, nor in 6 or 7. In addition to the clumsiness, verbosity and fragility that you point out, your solution actually has the problem that the ExecutionException that you are stripping off when you call getCause() actually contains most of the important stack trace information!

    That is, all the stack information of the thread executing the method in the code you presented is only in the ExcecutionException, and not in the nested causes, which only cover frames starting at call() in the Callable. That is, your doSomethingWithTimeout method won't even appear in the stack traces of the exceptions you are throwing here! You'll only get the disembodied stack from the executor. This is because the ExecutionException is the only one that was created on the calling thread (see FutureTask.get()).

    The only solution I know is complicated. A lot of the problem originates with the liberal exception specification of Callable - throws Exception. You can define new variants of Callable which specify exactly which exceptions they throw, such as:

    public interface Callable1<T,X extends Exception> extends Callable<T> {
    
        @Override
        T call() throws X; 
    }
    

    This allows methods which executes callables to have a more precise throws clause. If you want to support signatures with up to N exceptions, you'll need N variants of this interface, unfortunately.

    Now you can write a wrapper around the JDK Executor which takes the enhanced Callable, and returns an enhanced Future, something like guava's CheckedFuture. The checked exception type(s) are propagated at compile time from the creation and type of the ExecutorService, to the returned Futures, and end up on the getChecked method on the future.

    That's how you thread the compile-time type safety through. This means that rather than calling:

    Future.get() throws InterruptedException, ExecutionException;
    

    You can call:

    CheckedFuture.getChecked() throws InterruptedException, ProcessExecutionException, IOException
    

    So the unwrapping problem is avoided - your method immediately throws the exceptions of the required type and they are available and checked at compile time.

    Inside getChecked, however you still need to solve the "missing cause" unwrapping problem described above. You can do this by stitching the current stack (of the calling thread) onto the stack of the thrown exception. This a stretching the usual use of a stack trace in Java, since a single stack stretches across threads, but it works and is easy to understand once you know what is going on.

    Another option is to create another exception of the same thing as the one being thrown, and set the original as the cause of the new one. You'll get the full stack trace, and the cause relationship will be the same as how it works with ExecutionException - but you'll have the right type of exception. You'll need to use reflection, however, and is not guaranteed to work, e.g., for objects with no constructor having the usual parameters.

    0 讨论(0)
  • 2020-12-04 12:54

    I wouldn't say I recommend this, but here is a way you can do it. It is type-safe and whoever comes to modify it after you will probably be unhappy with it.

    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);
            }
        }
    
    }
    
    0 讨论(0)
  • 2020-12-04 12:55

    I'm not sure why you have the if/else block in the catch and instanceof, I think you can do what you want with:-

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

    One thing to consider, to reduce clutter, is to catch the exception inside your callable method and re-throw as your own domain/package specific exception or exceptions. How many exceptions you need to create would largely depend on how your calling code will respond to the exception.

    0 讨论(0)
提交回复
热议问题