Programación de Tareas con Quarz.Net

El post de Ayende de la semana pasada sobre planificación de tareas me ha recordado que tenía pendiente escrubir sobre una librería bastante útil para plafinicar tareas: Quarz.Net.

Muchas veces para resolver ciertos problemas necesitamos ejecutar tareas de forma periódica. No me refiero aquí a «comprobar cada X segundos si la conexión con la base de datos sigue activa» (aunque la solución que vamos a ver también sirve para estos casos), sino más bien a tareas del tipo «todos los lunes a las 0:00 revisar los cobros vencidos y notificar al departamento de facturación que los reclame«.

Para el primer tipo de procesos con un Timer te puedes apañar, pero para el segundo caso, existen básicamente 3 opciones:

  • Implementar nuestro propio sistema de programación de tareas. Si crees que ejecutar tareas en background de forma periódica es algo trivial, piénsalo un poco y léete los comentarios del post de Ayende con todas las cosas que se deben tener en cuenta y pueden salir mal.
  • Usar el planificador de tareas del sistema operativo para que lance nuestros procesos de forma automatica. Es una muy buena opción, que nos da mucha flexibilidad con multitud de opciones para establecer la planificación, reintentos, notificaciones en caso de fallo, etc. A cambio, tiene algunos inconvenientes. Obliga a tener un ejecutable por cada tarea que queramos realizar o, en su defecto, algún tipo de interfaz de línea de comandos para indicar a nuestro ejecutable la tarea que queremos que realice. Además, establece una dependencia adicional entre nuestra aplicación y el sistema operativo sobre el que está desplegada.
  • Utilizar alguna librería para realizar la planificación de tareas desde nuestra propia aplicación. Aquí es donde esta Quarz.Net, ya que nos permite realizar precisamente eso.

Conceptos básicos de Quarz.Net

Para trabajar con Quarz.Net necesitamos comprender cuáles son los conceptos básicos entorno a los que se organiza toda la libería:

Conceptos básicos de Quarz.Net

  • El Scheduler (planificador) es el motor de planificación de tareas como tal. A nivel de código, se corresponde con clases que implementan el interface IScheduler.
  • Los Jobs (trabajos) son los tareas que queremos realizar y que, para ello, registramos en el Scheduler. En código, son clases que implementan el interface IJob.
  • Los Triggers (disparadores) son los que marcan las condiciones bajo las cuales se deberá iniciar una tarea. Por ejemplo, cada hora en el minuto 17 o cada 3 minutos. En código, son clases que heredan de Trigger.

Es importante tener claro que en Quarz.Net existe una separación entre lo que hacemos (Job) y cuándo lo hacemos (Trigger). Gracias esto obtenemos una gran flexibilidad porque podemos tener tareas que se disparan con varios Triggers diferentes o Triggers que disparan varias tareas a la vez. Un buen ejemplo de separación de responsabilidades.

Cómo usar Quarz.Net

Junto a la documentación de Quarz.Net (que está muy bien, por cierto) puedes encontrar un completo tutorial para empezar a trabajar con Quarz.Net. De todas formas, para que puedas hacerte una idea rápida de cómo funciona, vamos a ver un ejemplo muy sencillo.

Empezamos creado un Job muy básico:

public class SampleJob : IJob
{
	public void Execute(JobExecutionContext context)
	{
		Console.Out.WriteLine("Job executed at {0:HH:mm:ss}", DateTime.Now);    
	}
}

Para crear un Job tenemos que crear una clase que implemente el interfaz IJob, que únicamente contiene el método Execute, en el cual deberemos hacer lo que sea que tenga que hacer nuestra tarea. Vamos, un patrón Command de toda la vida. Es importante que tenga un constructor sin parámetros.

El Job recibe un JobExecutionContext que nos permite acceder, entre otras cosas al Trigger que ha desencadenado la ejecución o la hora a la que está prevista la siguiente ejecución de la tarea.

Ahora necesitamos un Trigger para disparar nuestro Job:

var trigger = new CronTrigger("MyTrigger")
{
	CronExpressionString = "0 0 2 ? * *"
}

Quarz.Net incluye dos tipos de Triggers, el SimpleTrigger y el CronTrigger. En este caso estamos creando un CronTrigger que se disparará todos los días a las 2 de la mañana. Jugando con el CronExpressionString podemos usar la sintaxis del comando cron de Unix para crear planificaciones más complejas, del estilo de todos los martes a las 4 de la tarde, el último viernes de cada mes, etc.

Una vez que tenemos nuestro Job y nuestro Trigger, necesitamos un planificador para que todo esto empiece a funcionar:

var schedulerFactory = new StdSchedulerFactory();
var scheduler = schedulerFactory.GetScheduler();
	
var job = new JobDetail("MySampleJob", typeof (SampleJob));
scheduler.ScheduleJob(job, trigger);

El Scheduler lo obtenemos a través de un StdSchedulerFactory y le añadimos la programación de la tarea.

Ya sólo nos queda arrancar el planificador para que empiece a ejecutar tareas según la planificación establecida:

scheduler.Start();

Console.Out.WriteLine("Scheduler started. Press <ENTER> to quit.");
Console.ReadLine();

scheduler.Shutdown(true);

Una última cosa. Quarz.Net utiliza Common.Logging como sistema de logging, lo que nos permite aprovechar los distintos adapters de Common.Logging para usar la librería de logging que más nos guste: log4net, nlog, etc.

Si queremos activar el sistema de log con log4net, deberemos incluir algo parecido a esto para inicializarlo antes de empezar a utilizar Quarz.Net:

LogManager.Adapter = new Log4NetLoggerFactoryAdapter(new NameValueCollection()
{
	{"configType", "FILE-WATCH"},
	{"configFile", Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log4net.config")}
});

Resumiendo…

Quarz.Net es una librería muy útil para planificar tareas. En mi trabajo del Mundo Real ™ llevamos bastante tiempo usándolo para lanzar procesos internos desde un servicio de Windows y la verdad es que funciona muy bien y es muy estable.

Desde el punto de vista de implementación hay algunas cosas que no me gustan. Se nota que es un port de una librería de Java (Quarz) y hay cosas que me resultan poco idiomáticas en .NET, como el StdSchedulerFactory o la falta de sobrecargas genéricas para algunos métodos, que obligan a escribir cosas como new JobDetails("The Job", typeof(MyJob)) en lugar de new JobDetails<MyJob>.

Tampoco me acaba de gustar el uso de Common.Logging, pero comprendo que este es uno de los casos en que tiene sentido montar una capa de abstracción sobre la librería de logging para poder usar Quarz.Net en aplicaciones que ya estén usando alguna librería de logging. De hecho, es de los pocos casos en que le veía sentido a Common Service Locator (un caso similar) cuando me quejaba de él hace tiempo.

En este post sólo hemos visto una introducción rápida, pero con Quarz.Net se pueden hacer cosas más complicadas, como guardar la planificación en una base de datos y así repartir la carga de la ejecución de tareas entre distintas máquinas ejecutando Quarz.Net.

En definitiva, la próxima vez que tengas que implementar tareas programadas, antes de usar una solución casera que puede acabar siendo demasiado frágil, puedes probar Quarz.Net.

2 comentarios en “Programación de Tareas con Quarz.Net

Comentarios cerrados.