处理来自Java ExecutorService任务的异常


213

我正在尝试使用Java的ThreadPoolExecutor类以固定数量的线程运行大量繁重的任务。每个任务都有很多地方,在这些地方可能会由于异常而失败。

我已经继承了子类,ThreadPoolExecutor并且重写了afterExecute应该提供运行任务时遇到的任何未捕获异常的方法。但是,我似乎无法使其正常工作。

例如:

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

该程序的输出是“一切都很好-情况正常!” 即使唯一提交给线程池的Runnable引发异常。任何线索这里发生了什么?

谢谢!


您从未询问过任务的未来,那里发生了什么。整个服务执行程序或程序都不会崩溃。捕获异常并将其包装在ExecutionException下。如果您调用future.get(),他是否会被重新抛出。PS:即使可运行程序错误完成,future.isDone()[请阅读真实的api名称]也将返回true。因为任务是真实完成的。
Jai Pandit

Answers:


156

文档

注意:如果将动作显式地或通过诸如提交之类的方法封装在任务(例如FutureTask)中,则这些任务对象会捕获并维护计算异常,因此它们不会导致突然终止,并且内部异常不会传递给此方法。 。

当您提交Runnable时,它将被包装在Future中。

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

7
谢谢,我最终使用了此解决方案。此外,以防万一有人感兴趣:其他人建议不要对ExecutorService进行子类化,但无论如何我还是这样做了,因为我想在任务完成时监视任务,而不是等待所有任务终止然后在返回的所有Future上调用get() 。
汤姆(Tom)2010年

1
要继承遗嘱执行人另一种方法是子类FutureTask并覆盖其“完成”方法

1
汤姆>>您能不能在示例子代码中张贴ExecutorService的子类,以在完成任务时监视任务……
jagamot 2010年

1
如果您使用的是ComplableFuture.runAsync,此答案将不起作用,因为afterExecute将包含一个包私有的对象,并且无法访问throwable。我通过包裹电话解决了这个问题。请参阅下面的答案。
mmm 2014年

2
我们是否需要检查将来是否完成future.isDone()?由于afterExecuteRunnable完成后运行,因此我假设future.isDone()总是会返回true
Searene,

248

警告:应注意,此解决方案将阻止调用线程。


如果要处理任务引发的异常,则通常最好使用Callable而不是Runnable

Callable.call() 被允许抛出检查异常,这些异常将传播回调用线程:

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

如果Callable.call()引发异常,则将其包裹在中ExecutionException,并由引发Future.get()

这可能比子类化更可取ThreadPoolExecutor。如果异常是可恢复的,它还使您有机会重新提交任务。


5
>允许Callable.call()引发检查的异常,并且这些异常将传播回调用线程: 请注意,只有在调用了future.get()其重载版本或其重载版本时,抛出的异常才会传播到调用线程。
nhylated

16
这是完美的,但是如果我并行运行任务并且不想阻止执行该怎么办?
Grigory Kislin'3

43
不要使用此解决方案,因为它破坏了使用ExecutorService的全部目的。ExecutorService是一种异步执行机制,能够在后台执行任务。如果在执行后立即调用future.get(),它将阻塞调用线程,直到任务完成。
user1801374

2
此解决方案的评级不应很高。Future.get()同步工作,并且将充当阻止程序,直到执行了Runnable或Callable为止,并且如上所述无法实现使用Executor服务的目的
超级汉斯,

2
正如#nhylated指出的,这值得一个jdk BUG。如果未调用Future.get(),则将忽略Callable的任何未捕获异常。设计非常糟糕。。。仅仅花了1天以上的时间来弄清楚一个库使用了这个库,而jdk却默默地忽略了异常。并且,它仍然存在于jdk12中。
本江

18

对于此行为的解释恰好afterdocjavadoc中

注意:如果将动作显式地或通过诸如提交之类的方法封装在任务(例如FutureTask)中,则这些任务对象会捕获并维护计算异常,因此它们不会导致突然终止,并且内部异常不会传递给此方法。 。


10

我通过包装提交给执行程序的提供的可运行对象来解决这个问题。

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

3
您可以使用的whenComplete()方法来提高可读性CompletableFuture
爱德华·维奇

@EduardWirch可以,但是您不能从whenComplete()中抛出异常
Akshat

7

我正在使用jcabi-log中的VerboseRunnable类,该类可吞下所有异常并将其记录下来。非常方便,例如:

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

3

另一种解决方案是使用ManagedTaskManagedTaskListener

您需要实现接口ManagedTaskCallableRunnable

该方法getManagedTaskListener返回所需的实例。

public ManagedTaskListener getManagedTaskListener() {

然后在ManagedTaskListener中实现该taskDone方法:

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

有关托管任务生命周期和侦听器的更多详细信息。


2

这有效

  • 它是从SingleThreadExecutor派生的,但是您可以轻松调整它
  • Java 8 lamdas代码,但易于修复

它将创建一个具有单个线程的Executor,该线程可以完成很多任务;并等待当前执行结束以下一个执行开始

如果发生错误或异常,则uncaughtExceptionHandler会捕获它

公共最终课程SingleThreadExecutorWithExceptions {

    公共静态ExecutorService newSingleThreadExecutorWithExceptions(最终Thread.UncaughtExceptionHandler uncaughtExceptionHandler){

        ThreadFactory factory =(Runnable可运行)-> {
            最终线程newThread =新线程(可运行,“ SingleThreadExecutorWithExceptions”);
            newThread.setUncaughtExceptionHandler((最终线程caugthThread,最终Throwable throwable)-> {
                uncaughtExceptionHandler.uncaughtException(caugthThread,throwable);
            });
            返回newThread;
        };
        返回新的FinalizableDelegatedExecutorService
                (新的ThreadPoolExecutor(1、1,
                        0L,TimeUnit.MILLISECONDS,
                        新的LinkedBlockingQueue(),
                        厂){


                    afterExecute(Runnable runnable,Throwable throwable)受保护的无效{
                        super.afterExecute(可运行,可抛出);
                        if(throwable == null && Future的可运行实例){
                            尝试{
                                未来的未来=(未来)可运行;
                                如果(future.isDone()){
                                    future.get();
                                }
                            } catch(CancellationException ce){
                                throwable = ce;
                            } catch(ExecutionException ee){
                                throwable = ee.getCause();
                            } catch(InterruptedException ie){
                                Thread.currentThread()。interrupt(); //忽略/重置
                            }
                        }
                        if(throwable!= null){
                            uncaughtExceptionHandler.uncaughtException(Thread.currentThread(),throwable);
                        }
                    }
                });
    }



    私有静态类FinalizableDelegatedExecutorService
            扩展DelegatedExecutorService {
        FinalizableDelegatedExecutorService(ExecutorService executor){
            超级(执行者);
        }
        受保护的void finalize(){
            super.shutdown();
        }
    }

    / **
     *仅公开ExecutorService方法的包装器类
     * ExecutorService实现。
     * /
    私有静态类DelegatedExecutorService扩展了AbstractExecutorService {
        私人最终ExecutorService e;
        DelegatedExecutorService(ExecutorService executor){e = executor; }
        public void execute(Runnable command){e.execute(command); }
        public void shutdown(){e.shutdown(); }
        公共列表shutdownNow(){返回e.shutdownNow(); }
        public boolean isShutdown(){return e.isShutdown(); }
        public boolean isTerminated(){return e.isTerminated(); }
        公共布尔awaitTermination(长时间超时,TimeUnit单位)
                引发InterruptedException {
            返回e.awaitTermination(timeout,unit);
        }
        公开将来提交(可运行任务){
            返回e.submit(task);
        }
        公开将来提交(可调用任务){
            返回e.submit(task);
        }
        公开将来提交(可运行任务,T结果){
            返回e.submit(任务,结果);
        }
        公共列表> invokeAll(集合>任务)
                引发InterruptedException {
            返回e.invokeAll(任务);
        }
        公共列表> invokeAll(Collection>任务,
                                             长超时,TimeUnit单位)
                引发InterruptedException {
            返回e.invokeAll(任务,超时,单位);
        }
        公共T invokeAny(Collection>任务)
                引发InterruptedException,ExecutionException {
            返回e.invokeAny(任务);
        }
        公共T invokeAny(Collection>任务,
                               长超时,TimeUnit单位)
                引发InterruptedException,ExecutionException,TimeoutException {
            返回e.invokeAny(任务,超时,单位);
        }
    }



    私人SingleThreadExecutorWithExceptions(){}
}

不幸的是,使用finalize有点不稳定,因为它只会被称为“稍后当垃圾收集器将其收集时”(或者在Thread的情况下,可能不会被dunno)...
rogerdpack


0

如果您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);
            });
}

0

这是因为AbstractExecutorService :: submit将您的内容包装runnableRunnableFuture(除了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;
}

然后execute将其传递给WorkerWorker.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);
    }
}

最后task.run();在上面的代码调用中将调用 FutureTask.run()。这是异常处理程序代码,因此,您没有得到预期的异常。

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

0

这类似于mmm的解决方案,但更容易理解。让您的任务扩展包装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
    }
}

-4

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


3
我也尝试过这样做,但是uncaughtException方法似乎从未被调用。我相信这是因为ThreadPoolExecutor类中的工作线程正在捕获异常。
汤姆(Tom)2010年

5
不会调用uncaughtException方法,因为ExecutorService的Submit方法在将来包装了Callable / Runnable。那里正在捕获异常。
埃米尔(Emil Sit)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.