web-dev-qa-db-fra.com

Gestion des exceptions à partir de Java tâches ExecutorService

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!

188
Tom

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);
        }
    }
}
145
nos

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.

234
skaffman

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. .

16
Drew Wills

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
);
9
momomo

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
);
6
yegor256

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 .

3
CSchulz

Cela marche

  • Il est dérivé de SingleThreadExecutor, mais vous pouvez l’adapter facilement.
  • Java 8 lamdas code, mais facile à réparer

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 () {} 
} 
1
obesga_tirant

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.

1
Cristian Botiza

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);
            });
}
0
Bass

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 appelle FutureTask.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);
    }
}
0