简体   繁体   English

处理来自 Java ExecutorService 任务的异常

[英]Handling exceptions from Java ExecutorService tasks

I'm trying to use Java's ThreadPoolExecutor class to run a large number of heavy weight tasks with a fixed number of threads.我正在尝试使用 Java 的ThreadPoolExecutor类来运行具有固定线程数的大量重量级任务。 Each of the tasks has many places during which it may fail due to exceptions.每个任务都有很多地方可能由于异常而失败。

I've subclassed ThreadPoolExecutor and I've overridden the afterExecute method which is supposed to provide any uncaught exceptions encountered while running a task.我将ThreadPoolExecutor子类化,并重写了afterExecute方法,该方法应该提供运行任务时遇到的任何未捕获的异常。 However, I can't seem to make it work.但是,我似乎无法让它工作。

For example:例如:

public class ThreadPoolErrors extends ThreadPoolExecutor {
    public ThreadPoolErrors() {
        super(  1, // core threads
                1, // max threads
                1, // timeout
                TimeUnit.MINUTES, // timeout units
                new LinkedBlockingQueue<Runnable>() // work queue
        );
    }

    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        if(t != null) {
            System.out.println("Got an error: " + t);
        } else {
            System.out.println("Everything's fine--situation normal!");
        }
    }

    public static void main( String [] args) {
        ThreadPoolErrors threadPool = new ThreadPoolErrors();
        threadPool.submit( 
                new Runnable() {
                    public void run() {
                        throw new RuntimeException("Ouch! Got an error.");
                    }
                }
        );
        threadPool.shutdown();
    }
}

The output from this program is "Everything's fine--situation normal!"该程序的输出是“一切正常——情况正常!” even though the only Runnable submitted to the thread pool throws an exception.即使提交给线程池的唯一 Runnable 抛出异常。 Any clue to what's going on here?有什么线索吗?

Thanks!谢谢!

WARNING : It should be noted that this solution will block the calling thread.警告:应该注意的是,这个解决方案会阻塞调用线程。


If you want to process exceptions thrown by the task, then it is generally better to use Callable rather than Runnable .如果要处理任务抛出的异常,那么通常使用Callable而不是Runnable更好。

Callable.call() is permitted to throw checked exceptions, and these get propagated back to the calling thread: Callable.call()允许抛出已检查的异常,这些异常会传播回调用线程:

Callable task = ...
Future future = executor.submit(task);
try {
   future.get();
} catch (ExecutionException ex) {
   ex.getCause().printStackTrace();
}

If Callable.call() throws an exception, this will be wrapped in an ExecutionException and thrown by Future.get() .如果Callable.call()抛出异常,这将被包装在ExecutionException并由Future.get()抛出。

This is likely to be much preferable to subclassing ThreadPoolExecutor .这可能比子类化ThreadPoolExecutor更可取。 It also gives you the opportunity to re-submit the task if the exception is a recoverable one.如果异常是可恢复的,它还使您有机会重新提交任务。

From the docs :文档

Note: When actions are enclosed in tasks (such as FutureTask) either explicitly or via methods such as submit, these task objects catch and maintain computational exceptions, and so they do not cause abrupt termination, and the internal exceptions are not passed to this method.注意:当Action显式或通过submit等方法包含在任务(如FutureTask)中时,这些任务对象会捕获并维护计算异常,因此不会导致突然终止,内部异常不会传递给该方法.

When you submit a Runnable, it'll get wrapped in a Future.当你提交一个 Runnable 时,它​​会被包裹在一个 Future 中。

Your afterExecute should be something like this:你的 afterExecute 应该是这样的:

public final class ExtendedExecutor extends ThreadPoolExecutor {

    // ...

    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        if (t == null && r instanceof Future<?>) {
            try {
                Future<?> future = (Future<?>) r;
                if (future.isDone()) {
                    future.get();
                }
            } catch (CancellationException ce) {
                t = ce;
            } catch (ExecutionException ee) {
                t = ee.getCause();
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
            }
        }
        if (t != null) {
            System.out.println(t);
        }
    }
}

The explanation for this behavior is right in the javadoc for afterExecute :这种行为的解释在afterExecutejavadoc 中是正确的:

Note: When actions are enclosed in tasks (such as FutureTask) either explicitly or via methods such as submit, these task objects catch and maintain computational exceptions, and so they do not cause abrupt termination, and the internal exceptions are not passed to this method.注意:当Action显式或通过submit等方法包含在任务(如FutureTask)中时,这些任务对象会捕获并维护计算异常,因此不会导致突然终止,内部异常不会传递给该方法.

I got around it by wrapping the supplied runnable submitted to the executor.我通过包装提交给执行程序的提供的 runnable 来解决它。

CompletableFuture.runAsync(() -> {
        try {
              runnable.run();
        } catch (Throwable e) {
              Log.info(Concurrency.class, "runAsync", e);
        }
}, executorService);

I'm using VerboseRunnable class from jcabi-log , which swallows all exceptions and logs them.我正在使用VerboseRunnable -log 中的VerboseRunnable类,它吞下所有异常并记录它们。 Very convenient, for example:非常方便,例如:

import com.jcabi.log.VerboseRunnable;
scheduler.scheduleWithFixedDelay(
  new VerboseRunnable(
    Runnable() {
      public void run() { 
        // the code, which may throw
      }
    },
    true // it means that all exceptions will be swallowed and logged
  ),
  1, 1, TimeUnit.MILLISECONDS
);

Another solution would be to use the ManagedTask and ManagedTaskListener .另一种解决方案是使用ManagedTaskManagedTaskListener

You need a Callable or Runnable which implements the interface ManagedTask .您需要实现接口ManagedTaskCallableRunnable

The method getManagedTaskListener returns the instance you want.方法getManagedTaskListener返回您想要的实例。

public ManagedTaskListener getManagedTaskListener() {

And you implement in ManagedTaskListener the taskDone method:然后在ManagedTaskListener 中实现taskDone方法:

@Override
public void taskDone(Future<?> future, ManagedExecutorService executor, Object task, Throwable exception) {
    if (exception != null) {
        LOGGER.log(Level.SEVERE, exception.getMessage());
    }
}

More details about managed task lifecycle and listener .有关托管任务生命周期和侦听器的更多详细信息。

This works这有效

  • It is derived from SingleThreadExecutor, but you can adapt it easily它是从 SingleThreadExecutor 派生的,但你可以很容易地适应它
  • Java 8 lamdas code, but easy to fix Java 8 lamdas 代码,但易于修复

It will create a Executor with a single thread, that can get a lot of tasks;它会创建一个单线程的Executor,可以得到很多任务; and will wait for the current one to end execution to begin with the next并将等待当前一个结束执行以开始下一个

In case of uncaugth error or exception the uncaughtExceptionHandler will catch it在 uncaugth 错误或异常的情况下, uncaughtExceptionHandler将捕获它

public final class SingleThreadExecutorWithExceptions {

    public static ExecutorService newSingleThreadExecutorWithExceptions(final Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {

        ThreadFactory factory = (Runnable runnable)  -> {
            final Thread newThread = new Thread(runnable, "SingleThreadExecutorWithExceptions");
            newThread.setUncaughtExceptionHandler( (final Thread caugthThread,final Throwable throwable) -> {
                uncaughtExceptionHandler.uncaughtException(caugthThread, throwable);
            });
            return newThread;
        };
        return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                        0L, TimeUnit.MILLISECONDS,
                        new LinkedBlockingQueue(),
                        factory){


                    protected void afterExecute(Runnable runnable, Throwable throwable) {
                        super.afterExecute(runnable, throwable);
                        if (throwable == null && runnable instanceof Future) {
                            try {
                                Future future = (Future) runnable;
                                if (future.isDone()) {
                                    future.get();
                                }
                            } catch (CancellationException ce) {
                                throwable = ce;
                            } catch (ExecutionException ee) {
                                throwable = ee.getCause();
                            } catch (InterruptedException ie) {
                                Thread.currentThread().interrupt(); // ignore/reset
                            }
                        }
                        if (throwable != null) {
                            uncaughtExceptionHandler.uncaughtException(Thread.currentThread(),throwable);
                        }
                    }
                });
    }



    private static class FinalizableDelegatedExecutorService
            extends DelegatedExecutorService {
        FinalizableDelegatedExecutorService(ExecutorService executor) {
            super(executor);
        }
        protected void finalize() {
            super.shutdown();
        }
    }

    /**
     * A wrapper class that exposes only the ExecutorService methods
     * of an ExecutorService implementation.
     */
    private static class DelegatedExecutorService extends AbstractExecutorService {
        private final ExecutorService e;
        DelegatedExecutorService(ExecutorService executor) { e = executor; }
        public void execute(Runnable command) { e.execute(command); }
        public void shutdown() { e.shutdown(); }
        public List shutdownNow() { return e.shutdownNow(); }
        public boolean isShutdown() { return e.isShutdown(); }
        public boolean isTerminated() { return e.isTerminated(); }
        public boolean awaitTermination(long timeout, TimeUnit unit)
                throws InterruptedException {
            return e.awaitTermination(timeout, unit);
        }
        public Future submit(Runnable task) {
            return e.submit(task);
        }
        public  Future submit(Callable task) {
            return e.submit(task);
        }
        public  Future submit(Runnable task, T result) {
            return e.submit(task, result);
        }
        public  List> invokeAll(Collection> tasks)
                throws InterruptedException {
            return e.invokeAll(tasks);
        }
        public  List> invokeAll(Collection> tasks,
                                             long timeout, TimeUnit unit)
                throws InterruptedException {
            return e.invokeAll(tasks, timeout, unit);
        }
        public  T invokeAny(Collection> tasks)
                throws InterruptedException, ExecutionException {
            return e.invokeAny(tasks);
        }
        public  T invokeAny(Collection> tasks,
                               long timeout, TimeUnit unit)
                throws InterruptedException, ExecutionException, TimeoutException {
            return e.invokeAny(tasks, timeout, unit);
        }
    }



    private SingleThreadExecutorWithExceptions() {}
}

如果您想监控任务的执行,您可以旋转 1 或 2 个线程(可能更多取决于负载)并使用它们从 ExecutionCompletionService 包装器中获取任务。

If your ExecutorService comes from an external source (ie it's not possible to subclass ThreadPoolExecutor and override afterExecute() ), you can use a dynamic proxy to achieve the desired behavior:如果您的ExecutorService来自外部源(即不可能子类化ThreadPoolExecutor并覆盖afterExecute() ),您可以使用动态代理来实现所需的行为:

public static ExecutorService errorAware(final ExecutorService executor) {
    return (ExecutorService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
            new Class[] {ExecutorService.class},
            (proxy, method, args) -> {
                if (method.getName().equals("submit")) {
                    final Object arg0 = args[0];
                    if (arg0 instanceof Runnable) {
                        args[0] = new Runnable() {
                            @Override
                            public void run() {
                                final Runnable task = (Runnable) arg0;
                                try {
                                    task.run();
                                    if (task instanceof Future<?>) {
                                        final Future<?> future = (Future<?>) task;

                                        if (future.isDone()) {
                                            try {
                                                future.get();
                                            } catch (final CancellationException ce) {
                                                // Your error-handling code here
                                                ce.printStackTrace();
                                            } catch (final ExecutionException ee) {
                                                // Your error-handling code here
                                                ee.getCause().printStackTrace();
                                            } catch (final InterruptedException ie) {
                                                Thread.currentThread().interrupt();
                                            }
                                        }
                                    }
                                } catch (final RuntimeException re) {
                                    // Your error-handling code here
                                    re.printStackTrace();
                                    throw re;
                                } catch (final Error e) {
                                    // Your error-handling code here
                                    e.printStackTrace();
                                    throw e;
                                }
                            }
                        };
                    } else if (arg0 instanceof Callable<?>) {
                        args[0] = new Callable<Object>() {
                            @Override
                            public Object call() throws Exception {
                                final Callable<?> task = (Callable<?>) arg0;
                                try {
                                    return task.call();
                                } catch (final Exception e) {
                                    // Your error-handling code here
                                    e.printStackTrace();
                                    throw e;
                                } catch (final Error e) {
                                    // Your error-handling code here
                                    e.printStackTrace();
                                    throw e;
                                }
                            }
                        };
                    }
                }
                return method.invoke(executor, args);
            });
}

This is because of AbstractExecutorService :: submit is wrapping your runnable into RunnableFuture (nothing but FutureTask ) like below这是因为AbstractExecutorService :: submit将您的runnable包装到RunnableFuture (只有FutureTask )中,如下所示

AbstractExecutorService.java

public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null); /////////HERE////////
    execute(ftask);
    return ftask;
}

Then execute will pass it to Worker and Worker.run() will call the below.然后execute将它传递给Worker并且Worker.run()将调用下面的。

ThreadPoolExecutor.java

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();           /////////HERE////////
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

Finally task.run();最后task.run(); in the above code call will call FutureTask.run() .在上面的代码中调用将调用FutureTask.run() Here is the exception handler code, because of this you are NOT getting the expected exception.这是异常处理程序代码,因此您没有得到预期的异常。

class FutureTask<V> implements RunnableFuture<V>

public void run() {
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {   /////////HERE////////
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

This is similar to mmm's solution, but a bit more understandable.这类似于mmm的解决方案,但更容易理解。 Have your tasks extend an abstract class that wraps the run() method.让您的任务扩展一个包含 run() 方法的抽象类。

public abstract Task implements Runnable {

    public abstract void execute();

    public void run() {
      try {
        execute();
      } catch (Throwable t) {
        // handle it  
      }
    }
}


public MySampleTask extends Task {
    public void execute() {
        // heavy, error-prone code here
    }
}

The doc's example wasn't giving me the results I wanted.医生的例子没有给我想要的结果。

When a Thread process was abandoned (with explicit interput(); s) Exceptions were appearing.当 Thread 进程被放弃时(使用显式interput(); s)出现异常。

Also I wanted to keep the "System.exit" functionality that a normal main thread has with a typical throw , I wanted this so that the programmer was not forced to work on the code having to worry on it's context (... a thread), If any error appears, it must either be a programming error, or the case must be solved in place with a manual catch... no need for overcomplexities really.此外,我想保留普通主线程具有的典型throw的“System.exit”功能,我想要这样,这样程序员就不会被迫处理代码而不得不担心它的上下文(......一个线程),如果出现任何错误,它必须是编程错误,或者必须通过手动捕获来解决问题......真的不需要过于复杂。

So I changed the code to match my needs.所以我更改了代码以满足我的需求。

    @Override 
    protected void afterExecute(Runnable r, Throwable t) { 
        super.afterExecute(r, t); 
        if (t == null && r instanceof Future<?>) { 
            Future<?> future = (Future<?>) r; 
            boolean terminate = false; 
                try { 
                    future.get(); 
                } catch (ExecutionException e) { 
                    terminate = true; 
                    e.printStackTrace(); 
                } catch (InterruptedException | CancellationException ie) {// ignore/reset 
                    Thread.currentThread().interrupt(); 
                } finally { 
                    if (terminate) System.exit(0); 
                } 
        } 
    }

Be cautious though, this code basically transforms your threads into a main thread Exception-wise, while keeping all it's parallel properties... But let's be real, designing architectures in function of the system's parallel mechanism ( extends Thread ) is the wrong approach IMHO... unless an event driven design is strictly required....but then... if that is the requirement the question is: Is the ExecutorService even needed in this case?... maybe not.不过要小心,这段代码基本上将你的线程转换成一个主线程异常明智,同时保持它的所有并行属性......但让我们成为现实,根据系统的并行机制( extends Thread )设计架构是错误的方法恕我直言......除非严格要求事件驱动设计......但是......如果这是要求,问题是:在这种情况下甚至需要 ExecutorService 吗?......也许不需要。

而不是子类化 ThreadPoolExecutor,我会为它提供一个ThreadFactory实例,该实例创建新线程并为它们提供UncaughtExceptionHandler

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM