73 votes

Planification de l'exception de gestion du service d'exécution en attente

J'utilise ScheduledExecutorService pour exécuter une méthode périodiquement.

Code:

ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture handle =
        scheduler.scheduleWithFixedDelay(new Runnable() {
             public void run() { 
                 //Effectuer la logique métier, une exception peut se produire
             }
        }, 1, 10, TimeUnit.SECONDS);

Ma question:

Comment continuer le planificateur, si run() lance une exception? Dois-je capturer toutes les exceptions dans la méthode run()? Ou existe-t-il une méthode de rappel intégrée pour gérer l'exception? Merci!

4voto

dorony Points 363

Inspiré par la solution de @MBec, j'ai écrit un joli wrapper générique pour ScheduledExecutorService qui:

  • capturera et affichera toute exception non gérée.
  • retournera un CompletableFuture Java 8 au lieu d'un Future.

:)

import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * Cette classe utilise comme wrapper pour la classe ScheduledExecutorService Java native.
 * Elle a été créée afin de traiter le scénario très désagréable de la mort silencieuse !
 * explication : chaque fois qu'une exception non gérée est lancée par une tâche en cours d'exécution par ScheduledExecutorService
 * le thread va mourir et l'exception mourra avec lui (rien ne se propagera vers le thread principal).
 *
 * Cependant, HonestScheduledExecutorService imprimera gracieusement l'exception lancée avec un message personnalisé/défaut,
 * et renverra également un CompletableFuture conforme à Java 8 pour votre commodité :)
 */
@Slf4j
public class HonestScheduledExecutorService {

    private final ScheduledExecutorService scheduledExecutorService;
    private static final String DEFAULT_FAILURE_MSG = "Une erreur s'est produite lors de l'exécution de la tâche planifiée.";

    HonestScheduledExecutorService(ScheduledExecutorService scheduledExecutorService) {
        this.scheduledExecutorService = scheduledExecutorService;
    }

    public CompletableFuture scheduleWithFixedDelay(Callable callable, String onFailureMsg, long initialDelay, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(onFailureMsg) ? DEFAULT_FAILURE_MSG : onFailureMsg;
        CompletableFuture delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, delay, unit);

        return delayed;
    }

    public CompletableFuture scheduleWithFixedDelay(Runnable runnable, String onFailureMsg, long initialDelay, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(onFailureMsg) ? DEFAULT_FAILURE_MSG : onFailureMsg;
        CompletableFuture delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, delay, unit);

        return delayed;
    }

    public CompletableFuture schedule(Callable callable, String failureMsg, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture delayed = new CompletableFuture<>();

        scheduledExecutorService.schedule(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, delay, unit);

        return delayed;
    }

    public CompletableFuture schedule(Runnable runnable, String failureMsg, long delay, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture delayed = new CompletableFuture<>();

        scheduledExecutorService.schedule(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, delay, unit);

        return delayed;
    }

    public CompletableFuture scheduleAtFixedRate(Callable callable, String failureMsg, long initialDelay, long period, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleAtFixedRate(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, period, unit);

        return delayed;
    }

    public CompletableFuture scheduleAtFixedRate(Runnable runnable, String failureMsg, long initialDelay, long period, TimeUnit unit) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture delayed = new CompletableFuture<>();

        scheduledExecutorService.scheduleAtFixedRate(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        }, initialDelay, period, unit);

        return delayed;
    }

    public CompletableFuture execute(Callable callable, String failureMsg) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture delayed = new CompletableFuture<>();

        scheduledExecutorService.execute(() -> {
            try {
                Object result = callable.call();
                delayed.complete(result);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        });

        return delayed;
    }

    public CompletableFuture execute(Runnable runnable, String failureMsg) {
        final String msg = StringUtils.isEmpty(failureMsg) ? DEFAULT_FAILURE_MSG : failureMsg;
        CompletableFuture delayed = new CompletableFuture<>();

        scheduledExecutorService.execute(() -> {
            try {
                runnable.run();
                delayed.complete(null);
            } catch (Throwable th) {
                log.error(msg, th);
                delayed.completeExceptionally(th);
            }
        });

        return delayed;
    }

    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
        return scheduledExecutorService.awaitTermination(timeout, unit);
    }

    public List shutdownNow() {
        return scheduledExecutorService.shutdownNow();
    }

    public void shutdown() {
        scheduledExecutorService.shutdown();
    }

}

0 votes

Alors que je ne suis pas certain que cela ait du sens pratique de créer ce genre de gestionnaire taille unique pour un service d'exécuteur, si vous allez le faire, pourquoi ne pas déclarer que vous implémentez l'interface ScheduledExecutorService, et marquez vos méthodes avec @Override pour clarifier vos intentions et laisser le compilateur vérifier votre travail?

0 votes

Eh bien, car les nouvelles méthodes n'ont pas la même signature.

4voto

Anderson Points 652

Un moyen élégant de capturer l'exception et de maintenir les tâches planifiées en vie.

Tout d'abord, définissez une interface fonctionnelle.

    @FunctionalInterface
    interface NoSuppressedRunnable extends Runnable {

        @Override
        default void run() {
            try {
                doRun();
            } catch (Exception e) {
                log.error("...", e);
            }
        }

        void doRun();

    }

Ensuite, exécutez la tâche de cette manière.

executorService.scheduleAtFixedRate((NoSuppressedRunnable) () -> {
    // Le compilateur suppose qu'il s'agit d'une implémentation de doRun() une fois que vous avez mis la conversion ci-dessus 
}, 0, 60L, TimeUnit.SECONDS);

2voto

Guru Points 43

Toute exception dans la méthode run() d'un thread qui est passé à (ScheduledExecutorService) n'est jamais relancée et si nous utilisons future.get() pour obtenir le statut, alors le thread principal attend indéfiniment

2 votes

Pour éviter le blocage, vous pouvez utiliser isDone() pour vérifier si le ScheduledFuture est terminé, puis, s'il est terminé, utilisez get() pour le faire lancer une ExecutionException que vous pouvez ensuite gérer. Ou peut-être simplement utiliser la méthode get avec des paramètres de délai d'attente réglés à zéro.

0 votes

@Martin pouvez-vous expliquer un peu plus, j'ai une application qui met en mode silencieux mon appareil dans des conditions particulières tout en tournant en arrière-plan. Ça fonctionne parfaitement jusqu'à ce qu'après un certain temps d'exécution l'application se fige et je ne peux rien faire d'autre que forcer l'arrêt de l'application et la redémarrer.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X