web-dev-qa-db-fra.com

Comment arrêter une tâche planifiée qui a été lancée à l'aide de l'annotation @Scheduled?

J'ai créé une tâche planifiée simple à l'aide de @Scheduled annotation.

 @Scheduled(fixedRate = 2000)
 public void doSomething() {}

Maintenant, je veux arrêter cette tâche quand elle n’est plus nécessaire.

Je sais qu'il pourrait exister une option pour vérifier un indicateur conditionnel au début de cette méthode, mais cela n'arrêtera pas l'exécution de cette méthode.

Y at-il quelque chose que le printemps fournit pour arrêter @Scheduled tâche ?

30
Sach141

Option 1: utilisation d'un post-processeur

Fournissez ScheduledAnnotationBeanPostProcessor et appelez explicitement postProcessBeforeDestruction(Object bean, String beanName) , pour le bean dont la planification doit être arrêtée.


Option 2: Maintenir une carte des beans cibles pour son avenir

private final Map<Object, ScheduledFuture<?>> scheduledTasks =
        new IdentityHashMap<>();

@Scheduled(fixedRate = 2000)
public void fixedRateJob() {
    System.out.println("Something to be done every 2 secs");
}

@Bean
public TaskScheduler poolScheduler() {
    return new CustomTaskScheduler();
}

class CustomTaskScheduler extends ThreadPoolTaskScheduler {

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period) {
        ScheduledFuture<?> future = super.scheduleAtFixedRate(task, period);

        ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task;
        scheduledTasks.put(runnable.getTarget(), future);

        return future;
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period) {
        ScheduledFuture<?> future = super.scheduleAtFixedRate(task, startTime, period);

        ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task;
        scheduledTasks.put(runnable.getTarget(), future);

        return future;
    }
}

Lorsque la planification d'un bean doit être arrêtée, vous pouvez rechercher la carte pour obtenir le Future correspondant et l'annuler explicitement.

15
Mahesh

Il y a un peu d'ambiguïté dans cette question

  1. Quand vous dites "arrêtez cette tâche", voulez-vous dire que vous devez arrêter de manière à ce qu'il soit récupérable ultérieurement (si oui, par programme, en utilisant une condition générée dans la même application? Ou une condition externe?)
  2. Exécutez-vous d'autres tâches dans le même contexte? (Possibilité de fermer l'application entière plutôt qu'une tâche) - Vous pouvez utiliser le point de terminaison actuator.shutdown dans ce scénario.

Ma meilleure hypothèse est que vous souhaitez arrêter une tâche en utilisant une condition susceptible de survenir dans la même application, de manière récupérable. Je vais essayer de répondre sur la base de cette hypothèse.

C’est la solution la plus simple possible à laquelle je peux penser. Cependant, je vais apporter des améliorations telles que le retour anticipé plutôt que imbriqué si s

@Component
public class SomeScheduledJob implements Job {

    private static final Logger LOGGER = LoggerFactory.getLogger(SomeScheduledJob.class);

    @Value("${jobs.mediafiles.imagesPurgeJob.enable}")
    private boolean imagesPurgeJobEnable;

    @Override
    @Scheduled(cron = "${jobs.mediafiles.imagesPurgeJob.schedule}")
    public void execute() {

        if(!imagesPurgeJobEnable){
            return;
        }
        Do your conditional job here...
   }

Propriétés pour le code ci-dessus

jobs.mediafiles.imagesPurgeJob.enable=true or false
jobs.mediafiles.imagesPurgeJob.schedule=0 0 0/12 * * ?
6
so-random-dude

Il y a quelque temps, mon projet exigeait que tout composant puisse créer une nouvelle tâche planifiée ou arrêter le planificateur (toutes les tâches). Alors j'ai fait quelque chose comme ça

@Configuration
@EnableScheduling
@ComponentScan
@Component
public class CentralScheduler {

    private static AnnotationConfigApplicationContext CONTEXT = null;

    @Autowired
    private ThreadPoolTaskScheduler scheduler;

    public static CentralScheduler getInstance() {
        if (!isValidBean()) {
            CONTEXT = new AnnotationConfigApplicationContext(CentralScheduler.class);
        }

        return CONTEXT.getBean(CentralScheduler.class);
    }

    @Bean
    public ThreadPoolTaskScheduler taskScheduler() {
        return new ThreadPoolTaskScheduler();
    }

    public void start(Runnable task, String scheduleExpression) throws Exception {
        scheduler.schedule(task, new CronTrigger(scheduleExpression));
    }

    public void start(Runnable task, Long delay) throws Exception {
        scheduler.scheduleWithFixedDelay(task, delay);
    }

    public void stopAll() {
        scheduler.shutdown();
        CONTEXT.close();
    }

    private static boolean isValidBean() {
        if (CONTEXT == null || !CONTEXT.isActive()) {
            return false;
        }

        try {
            CONTEXT.getBean(CentralScheduler.class);
        } catch (NoSuchBeanDefinitionException ex) {
            return false;
        }

        return true;
    }
}

Donc je peux faire des choses comme

Runnable task = new MyTask();
CentralScheduler.getInstance().start(task, 30_000L);
CentralScheduler.getInstance().stopAll();

N'oubliez pas que, pour certaines raisons, je l'ai fait sans avoir à me soucier de la concurrence. Sinon, il devrait y avoir une synchronisation.

5
Jairton Junior

Voici un exemple où nous pouvons arrêter, démarrer et répertorier également toutes les tâches en cours d'exécution planifiées:

@RestController
@RequestMapping("/test")
public class TestController {

private static final String SCHEDULED_TASKS = "scheduledTasks";

@Autowired
private ScheduledAnnotationBeanPostProcessor postProcessor;

@Autowired
private ScheduledTasks scheduledTasks;

@Autowired
private ObjectMapper objectMapper;

@GetMapping(value = "/stopScheduler")
public String stopSchedule(){
    postProcessor.postProcessBeforeDestruction(scheduledTasks, SCHEDULED_TASKS);
    return "OK";
}

@GetMapping(value = "/startScheduler")
public String startSchedule(){
    postProcessor.postProcessAfterInitialization(scheduledTasks, SCHEDULED_TASKS);
    return "OK";
}

@GetMapping(value = "/listScheduler")
public String listSchedules() throws JsonProcessingException{
    Set<ScheduledTask> setTasks = postProcessor.getScheduledTasks();
    if(!setTasks.isEmpty()){
        return objectMapper.writeValueAsString(setTasks);
    }else{
        return "No running tasks !";
    }
}

}

5
TinyOS

Prévu

Lorsque le processus printanier Scheduled, il effectuera une itération de chaque méthode annotée de cette annotation et organisera les tâches par beans comme le montre la source suivante:

private final Map<Object, Set<ScheduledTask>> scheduledTasks =
        new IdentityHashMap<Object, Set<ScheduledTask>>(16);

Annuler

Si vous souhaitez simplement annuler une tâche planifiée répétée, vous pouvez simplement procéder comme suit (voici un démo exécutable dans mon référentiel):

@Autowired
private ScheduledAnnotationBeanPostProcessor postProcessor;
@Autowired
private TestSchedule testSchedule;

public void later() {
    postProcessor.postProcessBeforeDestruction(test, "testSchedule");
}

Remarquer

Il trouvera le ScheduledTask de ce haricot et l'annulera un à un. Ce qu'il faut remarquer, c'est que la méthode en cours sera également arrêtée (comme le montre postProcessBeforeDestruction source).

    synchronized (this.scheduledTasks) {
        tasks = this.scheduledTasks.remove(bean); // remove from future running
    }
    if (tasks != null) {
        for (ScheduledTask task : tasks) {
            task.cancel(); // cancel current running method
        }
    }
0
Tony