En un proyecto de los que estoy trabajando (telefonía celular) me pasó que se tenía que realizar la activación y/o cambio de plan a N número de líneas telefónicas y la compra de los paquetes correspondientes a cada línea, el problema se presentó cuando el proveedor se tardaba X tiempo en realizar la activación y mi proceso lanzaba ambas peticiones con diferencia de milisegundos, por ello la compra de los paquetes fallaba en la mayoría de los casos.

Para este problema hay dos posibles soluciones:

  1. Detener el hijo de ejecución.
  2. Ejecutar la tarea en otro hilo posponiendo su ejecución por X tiempo.

Para la primera opción se presenta el inconveniente de que el proceso quedaría en espera hasta que sea el tiempo de ejecutar la tarea, por ello el resto de procesos que no dependen de él no se van a ejecutar hasta que éste termine.

En el segundo caso el único inconveniente es que podemos dejar muchos hilos abiertos si no tenemos un buen control. Yo me fui por la segunda opción, y es lo que voy a compartir en este post.

Para ello, creé una clase que a su vez tiene una clase anidada que extiende de TimerTask, con la cual se ejecutará nuestra “tarea”. Aquí se me presentó otro inconveniente, ¿cómo pasar mi código que debe ejecutar dicha tarea? para poder pasar código a ejecutar a dicha clase ocupé un Callable como una propiedad (con su respectivo Setter).

private Callable funtion;

public void setFuntion(Callable funtion) {
	this.funtion = funtion;
}

Ahora lo que necesita la clase para ser más dinámica es permitirle pasar el tiempo que se quiere esperar para que comience la ejecución (debe ser en milisegundos), yo creé una propiedad en la cual se guarda el tiempo de espera, con sus setters permití que se agregara el tiempo en segundos o minutos e internamente se hace la conversión a milisegundos.

private int seconds;

public void setSeconds(int seconds) {
	this.seconds = seconds * 1000;
}

public void setMinutes(int minutes) {
	this.seconds = minutes * 64 * 1000;
}

Ahora sí, veamos nuestra clase anidada, que es la que ejecutará nuestro código, dicha clase como lo mencioné anteriormente, debe extender de TimerTask y debemos sobrescribir el método run(). En dicho método debemos llamar a nuestro Callable.

class TareaProgramadaTask extends TimerTask {
	public void run() {
		try {
			logger.info("----- Tarea iniciada -----");
			funtion.call();
		} catch (Exception e) {
			logger.error("----- Tarea erronea -----", e);
		} finally {
			logger.info("----- Tarea terminada -----");
			timer.cancel(); 
			timer = null;// Terminate the timer thread
			System.gc();
		}
	}
}

Yo encapsulé la llamada al Callable en un try/catch con un finally para que al terminar el proceso, mandar llamar al Garbage Collector.

Por último, en la clase principal tendremos un método que se encargue de generar una instancia de nuestro task (TareaProgramadaTask) y realizar la ejecución en un Timer.

public void run() {
	try {
		logger.info("----- Programando tarea -----");
		timer = new Timer();
		timer.schedule(new TareaProgramadaTask(), seconds);
	} catch (Exception e) {
		logger.error("----- Fallo el programar la tarea -----", e);
	}
}

Cómo se puede ver en el código anterior, los segundos se le pasan al Timer, que es el que se encarga de programar la ejecución de la tarea a través del método run que sobrescribimos.

Con esto ya podemos realizar la programación de tareas en un tiempo específico en tiempo de ejecución, lo que nos permite realizar procesos de manera independiente del hilo principal.

A continuación, comparto el código completo de la clase Java:

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Callable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TareaProgramada {

	static final Logger logger = LoggerFactory.getLogger(TareaProgramada.class);

	private Timer timer;

	private int seconds;
	
	private Callable funtion;

	public void run() {
		try {
			logger.info("----- Programando tarea -----");
			timer = new Timer();
			timer.schedule(new TareaProgramadaTask(), seconds);
		} catch (Exception e) {
			logger.error("----- Fallo el programar la tarea -----", e);
		}
	}

	class TareaProgramadaTask extends TimerTask {

		public void run() {
			try {
				logger.info("----- Tarea iniciada -----");
				funtion.call();
			} catch (Exception e) {
				logger.error("----- Tarea erronea -----", e);
			} finally {
				logger.info("----- Tarea terminada -----");
				timer.cancel(); 
				timer = null;// Terminate the timer thread
	            System.gc();
			}
		}
	}

	public void setSeconds(int seconds) {
		this.seconds = seconds * 1000;
	}
	
	public void setMinutes(int minutes) {
		this.seconds = minutes * 64 * 1000;
	}

	public void setFuntion(Callable funtion) {
		this.funtion = funtion;
	}

}

Nota: para ejecutar nuestra clase, podemos utilizar el siguiente código:

TareaProgramada task = new TareaProgramada();
task.setSeconds(10);
task.setFuntion(new Callable() {
	@Override
	public Void call() throws Exception {
		//Todo
		return null;
	}
});
task.run();