Saltar al contenido

¿Cómo ejecutar cierta tarea todos los días a una hora particular usando ScheduledExecutorService?

No busques más por todo internet ya que llegaste al sitio exacto, tenemos la solución que necesitas encontrar y sin complicaciones.

Solución:

Al igual que con la versión actual de Java SE 8 con su excelente API de fecha y hora con java.time este tipo de cálculo se puede hacer más fácilmente en lugar de utilizar java.util.Calendar y java.util.Date.

  • Use la clase de fecha y hora, es decir, LocalDateTime de esta nueva API
  • Utilice la clase ZonedDateTime para manejar el cálculo específico de la zona horaria, incluidos los problemas de horario de verano. Encontrarás un tutorial y un ejemplo aquí.

Ahora, como ejemplo de muestra para programar una tarea con su caso de uso:

ZonedDateTime now = ZonedDateTime.now(ZoneId.of("America/Los_Angeles"));
ZonedDateTime nextRun = now.withHour(5).withMinute(0).withSecond(0);
if(now.compareTo(nextRun) > 0)
    nextRun = nextRun.plusDays(1);

Duration duration = Duration.between(now, nextRun);
long initalDelay = duration.getSeconds();

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);            
scheduler.scheduleAtFixedRate(new MyRunnableTask(),
    initalDelay,
    TimeUnit.DAYS.toSeconds(1),
    TimeUnit.SECONDS);

los initalDelay se calcula para pedirle al planificador que retrase la ejecución en TimeUnit.SECONDS. Los problemas de diferencia de tiempo con unidades de milisegundos e inferiores parecen ser insignificantes para este caso de uso. Pero aún puedes hacer uso de duration.toMillis() y TimeUnit.MILLISECONDS para manejar los cálculos de programación en milisegundos.

¿Y también TimerTask es mejor para esto o ScheduledExecutorService?

NO:ScheduledExecutorService aparentemente mejor que TimerTask. StackOverflow ya tiene una respuesta para ti.

De @PaddyD,

Todavía tiene el problema por el cual debe reiniciar esto dos veces al año si desea que se ejecute a la hora local correcta. scheduleAtFixedRate no lo reducirá a menos que esté satisfecho con la misma hora UTC durante todo el año.

Como están las cosas true y @PaddyD ya ha dado una solución (+1 para él), estoy proporcionando un ejemplo de trabajo con la API de fecha y hora de Java8 con ScheduledExecutorService. Usar hilo de demonio es peligroso

class MyTaskExecutor

    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
    MyTask myTask;
    volatile boolean isStopIssued;

    public MyTaskExecutor(MyTask myTask$) 
    
        myTask = myTask$;

    

    public void startExecutionAt(int targetHour, int targetMin, int targetSec)
    
        Runnable taskWrapper = new Runnable()

            @Override
            public void run() 
            
                myTask.execute();
                startExecutionAt(targetHour, targetMin, targetSec);
            

        ;
        long delay = computeNextDelay(targetHour, targetMin, targetSec);
        executorService.schedule(taskWrapper, delay, TimeUnit.SECONDS);
    

    private long computeNextDelay(int targetHour, int targetMin, int targetSec) 
    
        LocalDateTime localNow = LocalDateTime.now();
        ZoneId currentZone = ZoneId.systemDefault();
        ZonedDateTime zonedNow = ZonedDateTime.of(localNow, currentZone);
        ZonedDateTime zonedNextTarget = zonedNow.withHour(targetHour).withMinute(targetMin).withSecond(targetSec);
        if(zonedNow.compareTo(zonedNextTarget) > 0)
            zonedNextTarget = zonedNextTarget.plusDays(1);

        Duration duration = Duration.between(zonedNow, zonedNextTarget);
        return duration.getSeconds();
    

    public void stop()
    
        executorService.shutdown();
        try 
            executorService.awaitTermination(1, TimeUnit.DAYS);
         catch (InterruptedException ex) 
            Logger.getLogger(MyTaskExecutor.class.getName()).log(Level.SEVERE, null, ex);
        
    

Nota:

  • MyTask es una interfaz con función execute.
  • Mientras se detiene ScheduledExecutorService, Siempre usa awaitTermination después de invocar shutdown en él: siempre existe la posibilidad de que su tarea se bloquee / bloquee y el usuario esperaría para siempre.

El ejemplo anterior que di con Calender fue solo un idea que mencioné, evité el cálculo de la hora exacta y los problemas de horario de verano. Se actualizó la solución según la queja de @PaddyD

En Java 8:

scheduler = Executors.newScheduledThreadPool(1);

//Change here for the hour you want ----------------------------------.at()       
Long midnight=LocalDateTime.now().until(LocalDate.now().plusDays(1).atStartOfDay(), ChronoUnit.MINUTES);
scheduler.scheduleAtFixedRate(this, midnight, 1440, TimeUnit.MINUTES);

Si no tiene el lujo de poder usar Java 8, lo siguiente hará lo que necesite:

public class DailyRunnerDaemon

   private final Runnable dailyTask;
   private final int hour;
   private final int minute;
   private final int second;
   private final String runThreadName;

   public DailyRunnerDaemon(Calendar timeOfDay, Runnable dailyTask, String runThreadName)
   
      this.dailyTask = dailyTask;
      this.hour = timeOfDay.get(Calendar.HOUR_OF_DAY);
      this.minute = timeOfDay.get(Calendar.MINUTE);
      this.second = timeOfDay.get(Calendar.SECOND);
      this.runThreadName = runThreadName;
   

   public void start()
   
      startTimer();
   

   private void startTimer();
   
      new Timer(runThreadName, true).schedule(new TimerTask()
      
         @Override
         public void run()
         
            dailyTask.run();
            startTimer();
         
      , getNextRunTime());
   


   private Date getNextRunTime()
   
      Calendar startTime = Calendar.getInstance();
      Calendar now = Calendar.getInstance();
      startTime.set(Calendar.HOUR_OF_DAY, hour);
      startTime.set(Calendar.MINUTE, minute);
      startTime.set(Calendar.SECOND, second);
      startTime.set(Calendar.MILLISECOND, 0);

      if(startTime.before(now) 

No requiere bibliotecas externas y tendrá en cuenta el horario de verano. Simplemente pase la hora del día en la que desea ejecutar la tarea como Calendar objeto, y la tarea como un Runnable. Por ejemplo:

Calendar timeOfDay = Calendar.getInstance();
timeOfDay.set(Calendar.HOUR_OF_DAY, 5);
timeOfDay.set(Calendar.MINUTE, 0);
timeOfDay.set(Calendar.SECOND, 0);

new DailyRunnerDaemon(timeOfDay, new Runnable()

   @Override
   public void run()
   
      try
      
        // call whatever your daily task is here
        doHousekeeping();
      
      catch(Exception e)
      
        logger.error("An error occurred performing daily housekeeping", e);
      
   
, "daily-housekeeping");

Nota: la tarea del temporizador se ejecuta en un subproceso de Daemon que no se recomienda para realizar ninguna E / S. Si necesita utilizar un hilo de usuario, deberá agregar otro método que cancele el temporizador.

Si tiene que usar un ScheduledExecutorService, simplemente cambie el startTimer método a lo siguiente:

private void startTimer()

   Executors.newSingleThreadExecutor().schedule(new Runnable()
   
      Thread.currentThread().setName(runThreadName);
      dailyTask.run();
      startTimer();
   , getNextRunTime().getTime() - System.currentTimeMillis(),
   TimeUnit.MILLISECONDS);

No estoy seguro del comportamiento, pero es posible que necesite un método de detención que llame shutdownNow si bajas por el ScheduledExecutorService route, de lo contrario, su aplicación puede bloquearse cuando intente detenerla.

Comentarios y valoraciones

Al final de la página puedes encontrar las aclaraciones de otros desarrolladores, tú además eres capaz insertar el tuyo si lo crees conveniente.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *