java - ¿Cómo atrapar apropiadamente las Excepciones de tiempo de ejecución de los ejecutores?
concurrency executor (5)
Digamos que tengo el siguiente código:
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(myRunnable);
Ahora, si myRunnable
arroja RuntimeExcpetion
, ¿cómo puedo atraparlo? Una forma sería suministrar mi propia implementación de newSingleThreadExecutor()
a newSingleThreadExecutor()
y establecer custom uncaughtExceptionHandler
para los Thread
s que newSingleThreadExecutor()
de ella. Otra forma sería ajustar myRunnable
a un myRunnable
local (anónimo) que contenga un try-catch -block. Tal vez hay otras soluciones similares también. Pero ... de alguna manera esto se siente sucio, siento que no debería ser tan complicado. ¿Hay una solución limpia?
¿Por qué no llamar a ExecutorService#submit()
, recuperar el Future
y manejar las posibles excepciones usted mismo al llamar a Future#get()
?
Decora el ejecutable en otro ejecutable que capta las excepciones de tiempo de ejecución y las maneja:
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));
La solución alternativa es utilizar ExecutorService.submit()
lugar de execute()
. Esto le devuelve un Future
que puede usar para recuperar el resultado o la excepción de la tarea:
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();
}
skaffman tiene razón en que usar submit
es el enfoque más limpio. Un enfoque alternativo es subclase ThreadPoolExecutor
y anular afterExecute(Runnable, Throwable)
. Si sigue este enfoque, asegúrese de llamar a execute(Runnable)
lugar de submit(Runnable)
o afterExecute
no se invocará.
Por la descripción de la API:
Método invocado al finalizar la ejecución del Runnable dado. Este método es invocado por el hilo que ejecutó la tarea. Si no es nulo, el Throwable es el
RuntimeException
oError
RuntimeException
que provocó que la ejecución finalizara abruptamente.Nota: Cuando las acciones se envuelven en tareas (como FutureTask) de forma explícita o mediante métodos como enviar, estos objetos de tareas capturan y mantienen excepciones computacionales, por lo que no causan una terminación abrupta, y las excepciones internas no se pasan a este método .
una tarea ( Callable
o Runnable
) enviada a ThreadPoolExecutors
se convertirá en una FuturnTask
, que contiene una propiedad llamada callable
igual a la tarea que envía. FuturnTask tiene su propio método de run
siguiente manera. Toda excepción o c.call()
en c.call()
será atrapado y puesto en un outcome
llamado prop. Al llamar al método de obtención de FuturnTask, se arrojará el outcome
FuturnTask.run del código fuente Jdk1.8
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);
}
}
...
}
si quieres ver la excepción:
- 1. La respuesta de skaffman
- 2. sobrescribe `afterExecute` cuando usted nuevo un 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
}
}