java - tick - ¿Cómo detener una tarea programada que se inició con la anotación @Scheduled?
segundero java (5)
Opción 1: uso de un postprocesador
Proporcione
ScheduledAnnotationBeanPostProcessor
e invoque explícitamente
postProcessBeforeDestruction(Object bean, String beanName)
, para el bean cuya programación debe detenerse.
Opción 2: mantener un mapa de beans objetivo para su futuro
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;
}
}
Cuando se debe detener la programación de un bean, puede buscar el mapa para obtener el
Future
correspondiente y cancelarlo explícitamente.
He creado una tarea programada simple usando la anotación
@Scheduled
Spring Framework.
@Scheduled(fixedRate = 2000)
public void doSomething() {}
Ahora quiero detener esta tarea, cuando ya no sea necesaria.
Sé que podría haber una alternativa para verificar un indicador condicional al comienzo de este método, pero esto no detendrá la ejecución de este método.
¿Hay algo que Spring proporcione para detener la tarea
@Scheduled
?
Programado
Cuando el proceso de primavera está
Scheduled
, iterará cada método anotado esta anotación y organizará las tareas por beans como muestra la siguiente fuente:
private final Map<Object, Set<ScheduledTask>> scheduledTasks =
new IdentityHashMap<Object, Set<ScheduledTask>>(16);
Cancelar
Si solo desea cancelar una tarea programada repetida, puede hacer lo siguiente (aquí hay una demostración ejecutable en mi repositorio):
@Autowired
private ScheduledAnnotationBeanPostProcessor postProcessor;
@Autowired
private TestSchedule testSchedule;
public void later() {
postProcessor.postProcessBeforeDestruction(test, "testSchedule");
}
aviso
Encontrará el
ScheduledTask
este
ScheduledTask
y lo cancelará uno por uno.
Lo que debe notarse es que también detendrá el método de ejecución actual (como muestra la fuente
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
}
}
Aquí hay un ejemplo en el que podemos detener, iniciar y enumerar también todas las tareas de ejecución programadas:
@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 !";
}
}
}
Hace algún tiempo tuve este requisito en mi proyecto de que cualquier componente debería poder crear una nueva tarea programada o detener el programador (todas las tareas). Entonces hice algo como esto
@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;
}
}
Entonces puedo hacer cosas como
Runnable task = new MyTask();
CentralScheduler.getInstance().start(task, 30_000L);
CentralScheduler.getInstance().stopAll();
Tenga en cuenta que, por alguna razón, lo hice sin tener que preocuparme por la concurrencia. Debería haber alguna sincronización de lo contrario.
Hay un poco de ambigüedad en esta pregunta
- Cuando dijo "detener esta tarea", ¿quiso parar de tal manera que luego sea recuperable (si es así, programáticamente, utilizando una condición que surge en la misma aplicación? O condición externa?)
- ¿Estás ejecutando otras tareas en el mismo contexto? (Posibilidad de cerrar la aplicación completa en lugar de una tarea): puede utilizar el punto final actuator.shutdown en este escenario
Mi mejor conjetura es que está buscando cerrar una tarea utilizando una condición que puede surgir en la misma aplicación, de manera recuperable. Trataré de responder en base a esta suposición.
Esta es la solución más simple posible en la que puedo pensar. Sin embargo, haré algunas mejoras como el retorno temprano en lugar de anidarse 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...
}
Propiedades para el código anterior
jobs.mediafiles.imagesPurgeJob.enable=true or false
jobs.mediafiles.imagesPurgeJob.schedule=0 0 0/12 * * ?