【问题标题】:How to properly catch RuntimeExceptions from Executors?如何从 Executors 中正确捕获 RuntimeExceptions?
【发布时间】:2010-12-13 20:44:51
【问题描述】:

假设我有以下代码:

ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(myRunnable);

现在,如果 myRunnable 抛出 RuntimeExcpetion,我该如何捕捉它?一种方法是将我自己的ThreadFactory 实现提供给newSingleThreadExecutor(),并为由此产生的Threads 设置自定义uncaughtExceptionHandlers。另一种方法是将myRunnable 包装到包含try-catch 块的本地(匿名)Runnable。也许还有其他类似的解决方法。但是……总觉得这很脏,我觉得不应该这么复杂。有干净的解决方案吗?

【问题讨论】:

  • 老实说,我质疑捕获不同线程中抛出的异常的意义。当前线程是否必须join线程并等待抛出异常?你没有在问题中提到这一点。
  • @BalusC:将异常从工作线程编组回调用线程是许多应用程序的常见要求。例如,UI 应用程序可能会调用 SwingWorker 线程来进行一些后台处理。如果处理失败,则需要将异常传递回 Event Dispatch 线程。
  • 这是一个常见的要求。线程 1 生成一些工作,通过线程 2 执行它,但需要了解它是否成功(即抛出异常)。 Executor 框架可以帮助您解决这个问题。
  • 嗯,其实我还没想过这么多。我只是好奇一般来说如何解决这个问题。但是人们似乎对下面的submit()Future 有一些很酷的说法:-)

标签: java concurrency executor runtimeexception


【解决方案1】:

提交给ThreadPoolExecutors 的任务(CallableRunnable)将转换为FuturnTask,包含一个名为callable 的道具等于您提交的任务。 FuturnTask 有自己的run 方法如下。在c.call() 中抛出的所有异常或可抛出对象都将被捕获并放入名为outcome 的道具中。调用FuturnTask的get方法时,会抛出outcome

来自Jdk1.8源码的FuturnTask.run

public void run() {
        ...
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    // save ex into `outcome` prop
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        }
        ...
    }

如果你想捕获异常:

      1. 斯卡夫曼的回答
      2. 新建 ThreadPoolExecutor 时覆盖 `afterExecute`
            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                super.afterExecute(r, t);
                Throwable cause = null;
                if (t == null && r instanceof Future) {
                    try {
                        ((Future<?>) r).get();
                    } catch (InterruptedException | ExecutionException e) {
                        cause = e;
                    }
                } else if (t != null) {
                    cause = t;
                }
                if (cause != null) {
                    // log error
                }
            }
    

    【讨论】:

      【解决方案2】:

      为什么不调用ExecutorService#submit(),取回Future,然后在调用Future#get()时自己处理可能的异常?

      【讨论】:

        【解决方案3】:

        skaffman 是正确的,因为使用 submit 是最干净的方法。另一种方法是继承ThreadPoolExecutor 并覆盖afterExecute(Runnable, Throwable)。如果您采用这种方法请务必调用execute(Runnable) 而不是submit(Runnable)afterExecute 将不会被调用。

        根据 API 描述:

        完成后调用的方法 执行给定的 Runnable。这 方法由线程调用 执行了任务。如果非空,则 Throwable 是未捕获的 RuntimeExceptionError 导致 执行突然终止。

        注意:当动作包含在 任务(例如 FutureTask) 显式地或通过诸如 提交,这些任务对象捕获并 维护计算异常,以及 所以它们不会导致突然 终止,和内部 异常不会传递给这个 方法

        【讨论】:

          【解决方案4】:

          在另一个runnable中装饰runnable,它捕获运行时异常并处理它们:

          public class REHandler implements Runnable {
              Runnable delegate;
              public REHandler (Runnable delegate) {
                  this.delegate = delegate;
              }
              public void run () {
                  try {
                      delegate.run ();
                  } catch (RuntimeException e) {
                      ... your fancy error handling here ...
                  }
              }
          }
          
          executor.execute(new REHandler (myRunnable));
          

          【讨论】:

            【解决方案5】:

            干净的解决方法是使用ExecutorService.submit() 而不是execute()。这会返回一个Future,您可以使用它来检索任务的结果或异常:

            ExecutorService executor = Executors.newSingleThreadExecutor();
            Runnable task = new Runnable() {
              public void run() {
                throw new RuntimeException("foo");
              }
            };
            
            Future<?> future = executor.submit(task);
            try {
              future.get();
            } catch (ExecutionException e) {
              Exception rootException = e.getCause();
            }
            

            【讨论】:

            • 谢谢,看起来和它打算的样子一模一样。干净。
            • 另外,您可能希望使用Callable 而不是Runnable,那么您的任务可以抛出已检查的异常以及未检查的异常。
            • getCause 在 1.6 和 1.7 中返回 Throwable 而不是异常
            • 问题是future.get() 是阻塞的,所以如果你希望能够异步运行你的任务是不行的
            • 正如@Loic 评论的那样,这个解决方案没有用,因为它首先破坏了使用 Executors 的全部目的。
            猜你喜欢
            • 1970-01-01
            • 2015-10-14
            • 2016-05-21
            • 2020-01-29
            • 2011-02-21
            • 1970-01-01
            • 1970-01-01
            • 2017-12-04
            • 1970-01-01
            相关资源
            最近更新 更多