How to stop a scheduled task that was started using @Scheduled annotation?

I have created a simple scheduled task using Spring Framework's @Scheduled annotation.

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

Now I want to stop this task, when no longer needed.

I know there could be one alternative to check one conditional flag at the start of this method, but this will not stop execution of this method.

Is there anything Spring provides to stop @Scheduled task ?


Option 1: Using a post processor

Supply ScheduledAnnotationBeanPostProcessor and explicitly invoke postProcessBeforeDestruction(Object bean, String beanName) , for the bean whose scheduling should be stopped.


Option 2: Maintaining a map of target beans to its Future

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;
    }
}

When the scheduling for a bean has to be stopped, you can lookup the map to get the corresponding Future to it and explicitly cancel it.


There is a bit of ambiguity in this question

  • When you say "stop this task", did you mean to stop in such a way that it's later recoverable (if yes, programmatically, using a condition which arises with in the same app? or external condition?)
  • Are you running any other tasks in the same context? (Possibility of shutting down the entire app rather than a task) -- You can make use of actuator.shutdown endpoint in this scenario
  • My best guess is, you are looking to shutdown a task using a condition that may arise with in the same app, in a recoverable fashion. I will try to answer based on this assumption.

    This is the simplest possible solution that I can think of, However I will make some improvements like early return rather than nested if 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...
       }
    

    Properties for the above code

    jobs.mediafiles.imagesPurgeJob.enable=true or false
    jobs.mediafiles.imagesPurgeJob.schedule=0 0 0/12 * * ?
    

    Some time ago I had this requirement in my project that any component should be able to create a new scheduled task or to stop the scheduler (all tasks). So I did something like this

    @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;
        }
    }
    

    So I can do things like

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

    Have in mind that, for some reasons, I did it without having to worry about concurrency. There should be some synchronization otherwise.

    链接地址: http://www.djcxy.com/p/50894.html

    上一篇: 获得Spring应用上下文

    下一篇: 如何停止使用@Scheduled注释启动的计划任务?