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

问题描述 投票:27回答:5

我使用Spring Framework的@Scheduled注释创建了一个简单的计划任务。

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

现在我想在不再需要时停止这项任务。

我知道在这个方法的开头可以有一个替代方法来检查一个条件标志,但是这不会停止执行这个方法。

是否有任何Spring提供停止@Scheduled任务?

java spring scheduled-tasks
5个回答
15
投票

Option 1: Using a post processor

提供ScheduledAnnotationBeanPostProcessor并显式调用postProcessBeforeDestruction(Object bean, String beanName),用于应该停止其调度的bean。

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

当必须停止bean的调度时,您可以查找映射以获取相应的Future并显式取消它。


6
投票

这个问题有点含糊不清

  1. 当你说“停止这个任务”时,你的意思是停止以后它可以恢复(如果是,以编程方式,使用在同一个应用程序中出现的条件?或外部条件?)
  2. 您是否在同一上下文中运行任何其他任务? (关闭整个应用程序而不是任务的可能性) - 您可以在此场景中使用actuator.shutdown端点

我最好的猜测是,您希望使用可能以可恢复的方式在同一个应用程序中出现的条件来关闭任务。我会尝试根据这个假设回答。

这是我能想到的simplest possible解决方案,但是我会做一些改进,比如早期返回而不是嵌套ifs

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

上述代码的属性

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

5
投票

前段时间我在我的项目中有这个要求,任何组件都应该能够创建一个新的计划任务或停止调度程序(所有任务)。所以我做了这样的事情

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

所以我可以做的事情

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

请记住,出于某些原因,我不必担心并发性。否则应该有一些同步。


2
投票

这是一个示例,我们可以停止,启动和列出所有计划的运行任务:

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

}


0
投票

Scheduled

当spring处理Scheduled时,它将迭代每个注释此注释的方法并按bean组织任务,如下面的源所示:

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

Cancel

如果您只想取消重复的计划任务,您可以执行以下操作(这是我的仓库中的runnable demo):

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

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

Notice

它会找到这个bean的ScheduledTask并逐一取消它。应该注意的是它还将停止当前运行的方法(如postProcessBeforeDestruction源显示)。

    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
        }
    }
© www.soinside.com 2019 - 2024. All rights reserved.