Java 1.6(およびEclipseから)のExecutors.newCachedThreadPool()
で実行されているFutureTask
が、Runnable.run()
メソッドの例外を飲み込むことを発見した後、私は私のすべてのRunnable
実装にthrow/catchを追加せずに、これらをキャッチする方法を考え出そうとしました。
APIは、FutureTask.setException()
をオーバーライドすることがこれに役立つはずであることを示唆しています。
このFutureがすでに設定されているかキャンセルされていない限り、このFutureは、指定されたthrowableを原因としてExecutionExceptionを報告します。このメソッドは、計算の失敗時に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
が呼び出されないのはなぜですか?
また、なぜThread.UncaughtExceptionHandler
メカニズムはFutureTask
によって使用されていませんが、これには何か理由がありますか?
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);
}
}
}
UncaughtExceptionHandler
を使用してみましたか?
UncaughtExceptionHandler
インターフェースを実装する必要があります。UncaughtExceptionHandler
を設定するには、Executor.newCachedThreadPool(ThreadFactory)
呼び出しで ThreadFactory
を指定します。setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
を介して設定できます。ExecutorService.execute
を使用してタスクを送信します。これは、execute
を使用して送信されたタスクからスローされた例外のみが、捕捉されなかった例外ハンドラーに到達するためです。 ExecutorService.submit
で送信されたタスクの場合、スローされた例外はすべて、タスクの戻り値の一部と見なされます。 submitで送信されたタスクが例外で終了した場合、 Future.get
を呼び出すときに再スローされ、ExecutionException
にラップされます。
はるかに優れたソリューション: Java FutureTask完了チェック
futureTask.get()
を呼び出して計算結果を取得すると、基になるRunnable
/Callable
が例外をスローした場合、例外(ExecutionException)がスローされます。
ExecutionException.getCause()
は、Runnable
/Callable
がスローした例外を返します。
Runnable
/Callable
がキャンセルされた場合も、別の例外がスローされます。
FutureTask
のソースコードを調べましたが、setException
が呼び出されている場所が見つかりませんでした。innerSetException
がスローされた場合に呼び出されるFutureTask.Sync
(FutureTask
の内部クラス)からのThrowable
メソッドがあります。 runメソッド。このメソッドはsetException
でも呼び出されています。
つまり、javadocが正しくない(または理解するのが非常に難しい)ように見えます。
3つの標準的な方法と1つの即興の方法があります。 1. UncaughtExceptionHandlerを使用して、作成されたスレッドのUncaughtExceptionHandlerを次のように設定します。
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable ex) {..}}
*ただし、スレッドによってスローされた例外をキャッチするという制限がありますが、将来のタスクの場合は飲み込まれます。 2.この目的のために特別に提供されたフック付きのカスタムthreadpoolexecutorを作成した後、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**();
..}
次に、3番目はcallメソッド内で単純なtry catchを使用していますが、ここの外では例外をキャッチできません。
回避策は、呼び出し可能オブジェクトを解放するファクトリであるTaskFactoryの呼び出しメソッドからすべての呼び出しメソッドを呼び出すことです。