16 Ejecutar una tarea de manera periódica¶
Ejecutar una tarea de manera periódica¶
Algunas veces queremos enviar al ejecutor una misma tarea de manera periódico, para que sea ejecutada cada cierto tiempo. Para ello, la interfaz ScheduledExecutorService
nos define el método scheduledAtFixedRate(runnable, initialDelay, period, timeUnit)
, que recibe como argumento la tarea a ejecutar en forma de Runnable
, el tiempo de espera para la primera ejecución (initial delay), el tiempo entre ejecuciones (period) y la unidad de tiempo en la que medir los parámetros anteriores (timeUnit). La llamada a este método retornará un objeto ScheduledFuture<?>
.
Debemos tener en cuenta que el periodo entre ejecuciones se refiere al momento del inicio de la ejecución, no al tiempo transcurrido desde la finalización de una ejecución anterior y el inicio de la siguiente.
Pero, ¿qué ocurre si, por ejemplo, la tarea tarda en ejecutarse 5 segundos y el periodo entre ejecuciones se establece en 3 segundos? Pues que la tarea vuelve a ser habilitada para ejecución 3 segundos después de que lo fuera la vez anterior. Sin embargo, es probable que la tarea esté en ese momento en ejecución. Cuando dicha ejecución finalice, y dado que la tarea ha vuelto a ser habilitada, puede comenzar a ejecutarse de nuevo inmediatamente.
Si en vez de querer habilitar para ejecución la tarea cada "x" tiempo lo que queremos es hacer que se vuelve a habilitar para ejecución cuando haya transcurrido tiempo desde que finalizara su última ejecución, entonces podemos usar el método scheduleWithFixedDelay(runnable, initialDelay, delay, timeUnit)
, muy parecido al anterior, pero en el que el parámetro relativo al periodo de tiempo (delay) hace referencia al tiempo transcurrido entre la finalización de la ejecución de la tarea y el inicio de la siguiente habilitación. La llamada a este método retornará un objeto ScheduledFuture<?>
.
Como vemos en ambos métodos la tarea debe ser enviada en forma de runnable, ya que no tiene sentido tener una tarea de cálculo en forma de callable que sea ejecutada periódicamente.
Warning
La versión 1.3 del JDK introdujo la clase java.util.Timer
, que permite planificar la ejecución de una tarea e incluso su ejecución periódica. Usa un único hilo trabajador, por lo que si sólo una de las tareas que le hayamos enviado podrá estar ejecutándose en un momento dado, incluso aunque por su delay inicial o periodicidad indicara que debiera estar ejecutándose.
La tareas con las que puede trabajar un objeto Timer
deberán ser objetos de la clase TimerTask
o de alguna clase que la extienda. Esta clase implementa la interfaz Runnable
, de manera que en su método run()
especificaremos el código que debe ejecutar la tarea.
La clase Timer
proporciona distintos métodos para poder enviarle una tarea, como por ejemplo schedule()
o scheduleAtFixedRate()
. Para cancelar la ejecución de las tareas enviadas objeto Timer
podemos llamar a su método cancel()
.
Se recomienda no usar las clases Timer o TimerTask, sino un ScheduledThreadPoolExecutor
, dado que éste ofrece una mayor flexibilidad en la planificación y puede emplear varios hilos de ejecución.
Proyecto ScheduledPeriodicTask¶
En este proyecto enviaremos la misma tarea de saludo del proyecto anterior pero esta vez para que se ejecute de forma periódica, de manera que transcurridos unos segundos se termina el ejecutor.
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
class Main {
public static void main(String[] args) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");
ScheduledThreadPoolExecutor scheduledExecutor =
(ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(1);
GreetTask greetTask = new GreetTask("Hello");
int periodSeconds = 5;
ScheduledFuture<?> greetScheduledFuture =
scheduledExecutor.scheduleAtFixedRate(greetTask, 5, periodSeconds, TimeUnit.SECONDS);
System.out.printf("%s -> %s - Greet task sent to be executed every %d seconds. Still %d seconds left to first execution\n",
Thread.currentThread().getName(),
dateTimeFormatter.format(LocalTime.now()),
periodSeconds,
greetScheduledFuture.getDelay(TimeUnit.SECONDS));
int sleepSeconds = 25;
try {
TimeUnit.SECONDS.sleep(sleepSeconds);
} catch (InterruptedException ignored) {
} finally {
System.out.printf("%s -> %s - Executor shut down after %d seconds\n",
Thread.currentThread().getName(),
dateTimeFormatter.format(LocalTime.now()),
sleepSeconds);
scheduledExecutor.shutdownNow();
}
}
}
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
class GreetTask implements Runnable {
private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");
private final String greet;
@SuppressWarnings("SameParameterValue")
GreetTask(String greet) {
this.greet = greet;
}
@Override
public void run() {
System.out.printf("%s -> %s - %s\n",
Thread.currentThread().getName(),
dateTimeFormatter.format(LocalTime.now()),
greet);
}
}