如何在 FutureTask 中捕获异常

Posted

技术标签:

【中文标题】如何在 FutureTask 中捕获异常【英文标题】:How to catch exceptions in FutureTask 【发布时间】:2011-04-03 02:07:28 【问题描述】:

在发现 FutureTask 在 Java 1.6(和来自 Eclipse)上运行在 Executors.newCachedThreadPool() 中会吞下 Runnable.run() 方法中的异常后,我试图想出一种方法来捕获这些异常而不添加 throw/catch致我所有的Runnable 实现。

API 建议覆盖 FutureTask.setException() 应该有助于:

导致这个 Future 报告一个 ExecutionException 并以给定的 throwable 作为它的原因,除非这个 Future 已经被设置或被取消。此方法在计算失败时由 run 方法在内部调用。

但是这个方法似乎没有被调用(使用调试器运行显示异常被FutureTask 捕获,但setException 没有被调用)。我编写了以下程序来重现我的问题:

public class RunTest 
    public static void main(String[] args) 
        MyFutureTask t = new MyFutureTask(new Runnable() 

            @Override
            public void run() 
                throw new RuntimeException("Unchecked exception");

            
        );

        ExecutorService service = Executors.newCachedThreadPool();
        service.submit(t);
    


public class MyFutureTask extends FutureTask<Object> 

    public MyFutureTask(Runnable r) 
        super(r, null);
    

    @Override
    protected void setException(Throwable t) 
        super.setException(t);
        System.out.println("Exception: " + t);
    

我的主要问题是:如何捕获 FutureTask 中抛出的异常?为什么setException 不被调用?

另外我想知道为什么FutureTask不使用Thread.UncaughtExceptionHandler机制,有什么原因吗?

【问题讨论】:

setException 很好用。我复制粘贴了你的代码,它可以工作。在任务上调用get() 方法时,您可能还想尝试ExecutionException 的try-catch。 使用 Callable/Future 组合的错误方式。 future.get() 方法为您提供包装在 ExecutionException 中的异常。 【参考方案1】:

我查看了FutureTask 的源代码,但找不到调用setException 的位置。FutureTask.SyncFutureTask 的内部类)中有一个 innerSetException 方法,在 run 方法抛出 Throwable 的情况下会被调用。这个方法也在setException中被调用。 所以看起来 javadoc 是不正确的(或者很难理解......)。

【讨论】:

在查看了极其缓慢的太阳虫数据库之后:bugs.sun.com/bugdatabase/view_bug.do?bug_id=6464365 显然它自 2006 年以来就已为人所知,但仍未修复(尽管它被声明为已修复) @Thirler - 错误报告清楚地表明它已在 Java 7 中修复。【参考方案2】:

setException 可能不是为覆盖而设计的,但它可以让您在需要时将结果设置为异常。您要做的是覆盖done() 方法并尝试获取结果:

public class MyFutureTask extends FutureTask<Object> 

    public MyFutureTask(Runnable r) 
        super(r, null);
    

    @Override
    protected void done() 
        try 
            if (!isCancelled()) get();
         catch (ExecutionException e) 
            // Exception occurred, deal with it
            System.out.println("Exception: " + e.getCause());
         catch (InterruptedException e) 
            // Shouldn't happen, we're invoked when computation is finished
            throw new AssertionError(e);
        
    

【讨论】:

我们如何从 ExecutionException 对象中提取状态码? @Min2 如果你想要引起ExecutionException的异常,你可以像我在println语句中那样使用getCause() @gustafc 我尝试了相同的方法,但使用 e.getCause() ,我能够获取 Throwable 对象和错误消息,但我需要检索存在状态代码的 networkResponse 对象。跨度> @Min2 只是将其向下转换为正确的类型(最好在 instanceof 检查之后)【参考方案3】:

您是否尝试过使用UncaughtExceptionHandler

需要实现UncaughtExceptionHandler接口。 要为池线程设置UncaughtExceptionHandler,请在Executor.newCachedThreadPool(ThreadFactory) 调用中提供ThreadFactory。 您可以通过setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)为创建的线程设置UncaughtExceptionHandler

使用ExecutorService.execute 提交任务,因为只有使用execute 提交的任务引发的异常才会进入未捕获的异常处理程序。对于使用ExecutorService.submit 提交的任务,任何抛出的异常都被视为任务返回值的一部分。如果使用 submit 提交的任务因异常而终止,则在调用 Future.get 时将其重新抛出,并包裹在 ExecutionException

【讨论】:

好收获!我错误地使用了ExecutorService.submit【参考方案4】:

更好的解决方案: Java FutureTask completion check

当您调用futureTask.get() 来检索计算结果时,如果底层Runnable/Callable 抛出异常,它将抛出异常(ExecutionException)。

ExecutionException.getCause() 将返回 Runnable/Callable 抛出的异常。

如果Runnable/Callable 被取消,它也会抛出不同的异常。

【讨论】:

【参考方案5】:

有三种标准方式和一种即兴方式。 1.使用UncaughtExceptionHandler,设置创建线程的UncaughtExceptionHandler为

Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() 
            public void uncaughtException(Thread t, Throwable ex) ..

*但限制是它捕获线程抛出的异常,但在未来任务的情况下,它会被吞噬。 2.使用afterExecute在使用专门为此目的提供的钩子制作自定义线程池执行器之后。翻看ThreadpoolExecutor的代码,通过submit > execute(有一个workQueue,workQueue.offer),将任务加入到工作队列中

   final void runWorker(Worker arg0) 
  Thread arg1 = Thread.currentThread();
  Runnable arg2 = arg0.firstTask;
  ..
     while(arg2 != null || (arg2 = this.**getTask()**) != null) 
        arg0.lock();
        ..
        try 
           this.beforeExecute(arg1, arg2);
           Object arg4 = null;
           try 
              arg2.run();
            catch (RuntimeException arg27) 
             ..
            finally 
              this.**afterExecute**(arg2, (Throwable)arg4);
           

  

getTask() ..
 this.workQueue.**poll**();
..

    那么,第三个就是在调用方法里面使用了简单的try catch,但是这里外面的异常是捕获不到的。

    解决方法是从 TaskFactory 的调用方法调用所有调用方法,TaskFactory 是一个释放可调用对象的工厂。

【讨论】:

以上是关于如何在 FutureTask 中捕获异常的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Promise 的回调中捕获未捕获的异常

lua中怎么捕获错误异常信息

如何让 pytest 不捕获异常

前端捕获异常技巧总结

如何在 ios 中捕获数据库异常?

如何从线程中捕获异常