Saltar al contenido

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

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 un 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 es cierto y @PaddyD ya le 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 utilizar 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) || startTime.equals(now))
      {
         startTime.add(Calendar.DATE, 1);
      }

      return startTime.getTime();
   }
}

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.

¡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 *