J'essaie d'utiliser la classe ThreadPoolExecutor
de Java pour exécuter un grand nombre de tâches lourdes avec un nombre fixe de threads. Chacune des tâches comporte de nombreux endroits où elle peut échouer en raison d’exceptions.
J'ai sous-classé ThreadPoolExecutor
et j'ai remplacé la méthode afterExecute
qui est supposée fournir toutes les exceptions non interceptées rencontrées lors de l'exécution d'une tâche. Cependant, je n'arrive pas à le faire fonctionner.
Par exemple:
public class ThreadPoolErrors extends ThreadPoolExecutor {
public ThreadPoolErrors() {
super( 1, // core threads
1, // max threads
1, // timeout
TimeUnit.MINUTES, // timeout units
new LinkedBlockingQueue<Runnable>() // work queue
);
}
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if(t != null) {
System.out.println("Got an error: " + t);
} else {
System.out.println("Everything's fine--situation normal!");
}
}
public static void main( String [] args) {
ThreadPoolErrors threadPool = new ThreadPoolErrors();
threadPool.submit(
new Runnable() {
public void run() {
throw new RuntimeException("Ouch! Got an error.");
}
}
);
threadPool.shutdown();
}
}
Le résultat de ce programme est "Tout va bien - la situation est normale!" même si le seul Runnable soumis au pool de threads lève une exception. Des indices sur ce qui se passe ici?
Merci!
De la docs :
Remarque: Lorsque des actions sont incluses dans des tâches (telles que FutureTask) de manière explicite ou via des méthodes telles que submit, ces objets de tâche capturent et gèrent des exceptions de calcul. Elles ne provoquent donc pas de terminaison abrupte et les exceptions internes ne sont pas transmises à cette méthode. .
Lorsque vous soumettez un Runnable, il sera enveloppé dans un futur.
Votre afterExecute devrait ressembler à ceci:
public final class ExtendedExecutor extends ThreadPoolExecutor {
// ...
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (t == null && r instanceof Future<?>) {
try {
Future<?> future = (Future<?>) r;
if (future.isDone()) {
future.get();
}
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
if (t != null) {
System.out.println(t);
}
}
}
WARNING: Il convient de noter que cette solution bloquera le thread appelant.
Si vous souhaitez traiter les exceptions émises par la tâche, il est généralement préférable d'utiliser Callable
plutôt que Runnable
.
Callable.call()
est autorisé à lancer des exceptions vérifiées, qui sont ensuite propagées au thread appelant:
Callable task = ...
Future future = executor.submit(task);
try {
future.get();
} catch (ExecutionException ex) {
ex.getCause().printStackTrace();
}
Si Callable.call()
lève une exception, elle sera encapsulée dans un ExecutionException
et levée par Future.get()
.
Cela sera probablement préférable au sous-classement de ThreadPoolExecutor
. Cela vous donne également la possibilité de soumettre à nouveau la tâche si l'exception est récupérable.
L'explication de ce comportement se trouve dans javadoc for afterExecute :
Remarque: Lorsque des actions sont incluses dans des tâches (telles que FutureTask) de manière explicite ou via des méthodes telles que submit, ces objets de tâche capturent et gèrent des exceptions de calcul. Elles ne provoquent donc pas de terminaison abrupte et les exceptions internes ne sont pas transmises à cette méthode. .
Je l'ai contourné en emballant le runnable fourni soumis à l'exécuteur testamentaire.
CompletableFuture.runAsync(
() -> {
try {
runnable.run();
} catch (Throwable e) {
Log.info(Concurrency.class, "runAsync", e);
}
},
executorService
);
J'utilise VerboseRunnable
la classe de jcabi-log , qui engloutit toutes les exceptions et les enregistre. Très pratique, par exemple:
import com.jcabi.log.VerboseRunnable;
scheduler.scheduleWithFixedDelay(
new VerboseRunnable(
Runnable() {
public void run() {
// the code, which may throw
}
},
true // it means that all exceptions will be swallowed and logged
),
1, 1, TimeUnit.MILLISECONDS
);
Une autre solution consisterait à utiliser ManagedTask et ManagedTaskListener .
Vous avez besoin d’un appelable ou exécutable qui implémente l’interface ManagedTask .
La méthode getManagedTaskListener
renvoie l'instance souhaitée.
public ManagedTaskListener getManagedTaskListener() {
Et vous implémentez dans ManagedTaskListener la méthode taskDone
:
@Override
public void taskDone(Future<?> future, ManagedExecutorService executor, Object task, Throwable exception) {
if (exception != null) {
LOGGER.log(Level.SEVERE, exception.getMessage());
}
}
Plus de détails sur cycle de vie de la tâche gérée et écouteur .
Cela marche
Cela créera un exécuteur avec un seul thread, qui peut avoir beaucoup de tâches; et attendra que l'exécution en cours prenne fin pour que l'exécution commence par la prochaine
En cas d'erreur d'inauguration ou d'exception, le ncaughtExceptionHandler l'attrapera
classe finale publique SingleThreadExecutorWithExceptions { public static ExecutorService newSingleThreadExecutorWithExceptions (final Thread.UncaughtExceptionHandler uncaughtExceptionHandler) { . . (. Run.) > {{.____.] fil final newThread = nouveau fil (exécutable, "SingleThreadExecutorWithExceptions"); newThread.setUncaughtExceptionHandler ((fil final caugthThread, final jetable joignable) -> { uncaughtExceptionHandler.ConnthHead, une exception. (caugthThread, throwable); }); return newThread; }; return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor (1, 1, 0L, TimeUnit.MILLISECONDS, New LinkedBlockingQueue (), Usine) { Prot ect void afterExecute (exécutable, jetable et jetable) { super.afterExecute (exécutable, jetable); if (throwable == null && instance exécutable de Future) { try { Future future = (Future) exécutable; If (future.isDone ()) { Future.get (); } } Capture (CancellationException ce) { Throwable = ce; } Capture (ExecutionException ee) { Jetable = ee.getCause (); } Capture (InterruptedException ie) { Thread.currentThread (). Interrupt (); // ignore/réinitialise } } if (throwable! = null) { uncaughtExceptionHandler.uncaughtException (Thread.currentThread (), throwable); } } }); } classe statique privée FinalizableDelegatedExecutorService s'étend DelegatedExecutorService { FinalisableDélégéExecutorService (ExecutorService exécuteur) { super (exécuteur); } protégé void finalize () { super .shutdown (); } } /** * Une classe wrapper qui expose uniquement les méthodes ExecutorService * d'une implémentation ExecutorService. */ classe statique privée DelegatedExecutorService prolongé AbstractExecutorService { private final ExecutorService e; DelegatedExecutorService (exécuteur ExecutorService) {e = exécuteur; } public void execute (commande exécutable) {e.execute (commande); } public void shutdown () {e.shutdown (); } public Liste shutdownNow () {return e.shutdownNow (); } public boolean isShutdown () {return e.isShutdown (); } public boolean isTerminated () {return e.isTerminated (); } public boolean waitTermination (long délai d'attente, unité TimeUnit) lève InterruptedException { return e.awaitTermination (délai d'attente, unité); } public Future soumettre (tâche exécutable) { renvoyer e.submit (tâche); } public futur soumettre (tâche appelable) { renvoyer e.submit ( [).]. } public Future (tâche exécutable, résultat T) { return e.submit (tâche, résultat); } Liste publique> invokeAll (Collection> tâches) lève une exception InterruptedException { renvoyer e.invokeAll (tâches); } liste publique> invokeAll (Collection> tâches , long timeout, unité TimeUnit) lève InterruptedException { return e.invokeAll (tâches, délai d'attente, unité); } public T invokeAny (Collection> tâches) lève InterruptedException, ExecutionException { renvoyer e.invokeAny (tâches); } public T invokeAny (Collection> tâches, long délai d'attente, unité TimeUnit) lève InterruptedException, ExecutionException, TimeoutException { renvoyer e.invokeAny (tâches, délai d'attente, unité); ]} } privé SingleThreadExecutorWithExceptions () {} }
Si vous souhaitez surveiller l'exécution d'une tâche, vous pouvez utiliser 1 ou 2 unités d'exécution (peut-être davantage en fonction de la charge) et les utiliser pour prendre des tâches à partir d'un wrapper ExecutionCompletionService.
Si votre ExecutorService
provient d'une source externe (c'est-à-dire qu'il n'est pas possible de sous-classer ThreadPoolExecutor
et de remplacer afterExecute()
), vous pouvez utiliser un proxy dynamique pour obtenir le comportement souhaité:
public static ExecutorService errorAware(final ExecutorService executor) {
return (ExecutorService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[] {ExecutorService.class},
(proxy, method, args) -> {
if (method.getName().equals("submit")) {
final Object arg0 = args[0];
if (arg0 instanceof Runnable) {
args[0] = new Runnable() {
@Override
public void run() {
final Runnable task = (Runnable) arg0;
try {
task.run();
if (task instanceof Future<?>) {
final Future<?> future = (Future<?>) task;
if (future.isDone()) {
try {
future.get();
} catch (final CancellationException ce) {
// Your error-handling code here
ce.printStackTrace();
} catch (final ExecutionException ee) {
// Your error-handling code here
ee.getCause().printStackTrace();
} catch (final InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
}
} catch (final RuntimeException re) {
// Your error-handling code here
re.printStackTrace();
throw re;
} catch (final Error e) {
// Your error-handling code here
e.printStackTrace();
throw e;
}
}
};
} else if (arg0 instanceof Callable<?>) {
args[0] = new Callable<Object>() {
@Override
public Object call() throws Exception {
final Callable<?> task = (Callable<?>) arg0;
try {
return task.call();
} catch (final Exception e) {
// Your error-handling code here
e.printStackTrace();
throw e;
} catch (final Error e) {
// Your error-handling code here
e.printStackTrace();
throw e;
}
}
};
}
}
return method.invoke(executor, args);
});
}
Ceci est dû au fait que AbstractExecutorService :: submit
enroule votre runnable
dans RunnableFuture
(rien que FutureTask
) comme ci-dessous
AbstractExecutorService.Java
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null); /////////HERE////////
execute(ftask);
return ftask;
}
Ensuite, execute
le transmettra à Worker
et Worker.run()
appellera ci-dessous.
ThreadPoolExecutor.Java
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run(); /////////HERE////////
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
Enfin,
task.run();
dans l'appel de code ci-dessus appelleFutureTask.run()
. Voici le code du gestionnaire d'exceptions, pour cette raison, vous n'obtenez PAS l'exception attendue.
class FutureTask<V> implements RunnableFuture<V>
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) { /////////HERE////////
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}