How to properly catch RuntimeExceptions from Executors?
Say that I have the following code:
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(myRunnable);
Now, if myRunnable
throws a RuntimeExcpetion
, how can I catch it? One way would be to supply my own ThreadFactory
implementation to newSingleThreadExecutor()
and set custom uncaughtExceptionHandler
s for the Thread
s that come out of it. Another way would be to wrap myRunnable
to a local (anonymous) Runnable
that contains a try-catch -block. Maybe there are other similar workarounds too. But... somehow this feels dirty, I feel that it shouldn't be this complicated. Is there a clean solution?
Solution 1:
The clean workaround is to use ExecutorService.submit()
instead of execute()
. This returns you a Future
which you can use to retrieve the result or exception of the task:
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();
}
Solution 2:
Decorate the runnable in another runnable which catches the runtime exceptions and handles them:
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));
Solution 3:
Why not call ExecutorService#submit()
, get the Future
back and then handle possible exceptions yourself when calling Future#get()
?
Solution 4:
skaffman is correct in that using submit
is the cleanest approach. An alternative approach is to subclass ThreadPoolExecutor
and override afterExecute(Runnable, Throwable)
. If you follow this approach be sure to call execute(Runnable)
rather than submit(Runnable)
or afterExecute
will not be invoked.
Per the API description:
Method invoked upon completion of execution of the given Runnable. This method is invoked by the thread that executed the task. If non-null, the Throwable is the uncaught
RuntimeException
orError
that caused execution to terminate abruptly.Note: When actions are enclosed in tasks (such as FutureTask) either explicitly or via methods such as submit, these task objects catch and maintain computational exceptions, and so they do not cause abrupt termination, and the internal exceptions are not passed to this method.
Solution 5:
a task(Callable
or Runnable
) submitted to ThreadPoolExecutors
will be convert to a FuturnTask
, contains a prop named callable
equals the task you submit. FuturnTask has its own run
method as follows. All exception or throwable throwed in c.call()
will be catched and put into a prop named outcome
. When calling FuturnTask's get
method, outcome
will be throwed
FuturnTask.run From Jdk1.8 Source Code
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);
}
}
...
}
if you want catch the exception :
- 1. skaffman's answer
- 2. overwrite `afterExecute` when you new a ThreadPoolExecutor
@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
}
}