如何在 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.Sync
(FutureTask
的内部类)中有一个 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 中捕获异常的主要内容,如果未能解决你的问题,请参考以下文章