lunes, 29 de abril de 2013

Utilizando Quartz.net en tu aplicación - Creando tareas programadas

Hola a todos, después de tanto tiempo vuelvo a postear en mi blog, muchas cosas pasaron en este tiempo, muchas cosas que aprendí y también muchas cosas que se deben postear :D. 

Pues bien, vayamos a la parte interesante con la que los aburrire en este post ;), primeramente dejenme presentarles a Quartz.net que es un Task Scheduler el cual nos permite programar tareas a ejecutar cada cierto tiempo según segundos, minutos o incluso según una expresión CRON de Unix, en palabras simples es un programador de tareas como el que tiene Windows. El proyecto Quartz.net está basado en el proyecto Quartz que originalmente fue creado para Java, este es mantenido por la comunidad y es un proyecto bastante maduro. 

Quartz.net tiene dos opciones: la primera es correr como un servicio windows y la segunda es embeber el "programador de tareas" a nuestra aplicación. Para este post utilizaremos la segunda opción, hay que resaltar que esta opción no es muy recomendada si la aplicación en la que correra el "programador de tareas" pueda ser detenida en algún momento, es decir, esto funcionará mientras la aplicación siga ejecutandose. 

Ahora vayamos a la práctica, para poder tener Quartz.net en nuestra aplicación debemos descargar las DLLs y adicionar una referencia en nuestra aplicación. El sitio de descarga para Quartz.Net es este, una vez descargada el archivo Zip este contendrá varias carpetas, algunas que contienen el servicio windows, ejemplos, etc. pero nosotros solo necesitamos Common.Logging.dll Quartz.dll ahora si se desea disfrutar del intellisense de Visual Studio, Sharp Develop o cualquier IDE con esta propiedad también se puede utilizar el archivo Quartz.xml. 

Creamos una nueva solución con un proyecto de tipo consola, en este caso la llamaré "QuartzNetEmbedded" y dentro del proyecto se adicionará referencia a las DLLs que se mencionaron anteriormente. 
Rerencias a Quartz.net
Para poder programar tareas necesitamos un scheduler o programador de tareas, a este se le podrá indicar que tareas se ejecutaran segun cierto patrón de tiempo o según un tiempo predeterminado. Una de las cosas interesantes de la arquitectura que tiene Quartz.net es que utiliza el patrón de diseño "Factory" lo que significa que si queremos un objeto que sea instancia de la clase o interfaz Scheduler debemos instanciar un Factory para instanciar esta clase o interfaz.

...
using Quartz;
using Quartz.Impl;

public class Program {
    public static void Main(string[] args) {
        StdSchedulerFactory factory = new StdSchedulerFactory();

        IScheduler scheduler = factory.GetScheduler();

        if (!scheduler.IsStarted) {
            scheduler.Start();
        }

        ...
    }
}


El código anterior crea un objeto que implementa la interfaz IScheduler luego verifica si el scheduler ya a sido iniciado, caso contrario lo inicializamos llamando al método Start(). Tal vez surja la duda de por qué verificar que el Scheduler este iniciado si esto lo instanciamos unas lineas de código más arriba?, pues la respuesta es simple, en ambientes reales pueden existir como los Jobs varios Schedulers entonces una sobrecarga del método GetScheduler() es la de pasar como argumento SchedulerName que es una cadena, pero para nuestro ejemplo no sería necesario verificar si este scheduler ha sido iniciado.

Una vez que se tiene un scheduler iniciado este esperará por Jobs a ejecutar respecto a un trigger. Se debe crear una clase que implemente la interfaz IJob y en un futuro el contenido del método Execute que esta clase tenga se ejecutará una vez asociado el job al scheduler. Nuestro primer job será de la siguiente manera:
    
...
using Quartz;
public class PrimerJob : IJob {
    public void Execute(IJobExecutionContext context) {
        // El codigo que se ejecutara cuando este job sea invocado
        Console.ForegroundColor = ConsoleColor.Yellow;
        Console.WriteLine("===========================================");

        Console.ForegroundColor = ConsoleColor.DarkRed;
        Console.WriteLine("Proceso ejecutado a hrs: " + DateTime.Now.ToString());

        Console.ForegroundColor = ConsoleColor.Yellow;
        Console.WriteLine("===========================================");
    }
}
    
Esta clase tiene un método Execute que pertenece a la interfaz IJob y este deberá contener el código que se desee ejecutar cada vez que el job sea llamado. 

Bueno ahora que se tiene el scheduler y una clase que ejecutará lo que se desee es momento de ir a la parte interesante en la cual que enlazaremos este job con al scheduler y haremos que se ejecute cada minuto utilizando una expresión CRON (la expresión CRON utilizada en este ejemplo fue creada en el sitio web: CRON Maker). Los pasos que seguiremos son los siguientes:
  1. Instanciar PrimerJob en un objeto de tipo IJobDetail através de la clase JobBuilder
  2. Instanciar un trigger de tipo ICronTrigger utilizando una expresión CRON
  3. Enlazar el job y el trigger al scheduler, lo que significa que el job se ejecutará dependiendo como se haya configurado el trigger
    
IJobDetail primerJob = JobBuilder.Create<PrimerJob>()
    .WithIdentity("PrimerJob", "Grupo")
    .RequestRecovery()
    .Build();

// Crea un trigger de tipo CRON que es un formato utilizado en
// UNIX en el que se puede programar una tarea. Una herramienta
// para crear este tipo de expresiones es:
// La expresion "0 0/1 * 1/1 * ? *" significa que se ejecutara cada min
ICronTrigger cronTrigger = (ICronTrigger)TriggerBuilder.Create()
    .WithIdentity("PrimerTrigger", "Grupo")
    .WithCronSchedule("0 0/1 * 1/1 * ? *")
    .StartAt(DateTime.UtcNow)
    .Build();

// En un entorno real pueden existir varios Jobs ejecutandose
// para evitar problemas verificamos si nuestro proceso ya
// existe en el programador de tares
JobKey jobKey = new JobKey("PrimerJob", "Grupo");
if (scheduler.CheckExists(jobKey)) {
    scheduler.DeleteJob(jobKey);
}

// Ahora se puede programar cada cierto tiempo
// establecido en el trigger que se ejecute nuestro primer job
scheduler.ScheduleJob(primerJob, cronTrigger);
Console.WriteLine("Se programo un tarea a ejecutar cada minuto");
    
Para el trigger se indica a que hora iniciará el job asociado, pero sea la hora que sea, se ejecutará cuando sea el segundo 60, es decir si la hora actual es 23:33:02 el trigger se lanzará por primera vez a horas 23:34:00 generalmente con un segundo de error. 

El código fuente utilizado en este post esta disponible aquí

Pues bien este seria el fin de este post que tenia como objetivo mostrar un funcionamiento básico de Quartz.net embebido a una aplicación y no dependiente del servicio windows que trae por defecto. Para tener más información ejemplos(desactualizado) o la documentación oficial sobre esta muy útil libreria visiten el sitio de Quartz.net

Espero les haya gustado este post, cualquier duda pueden buscarme por email o twitter. 


 Saludos

5 comentarios:

  1. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  2. Buenas tardes, Una pregunta cuando se ejecuta la tarea????, cuando se inicia el programa???

    ResponderEliminar
  3. cuando ejecuto la tarea me dice que no puede leer el emsamblado quarts aunque haya referenciado las dll

    ResponderEliminar
  4. Lo he intentado usar, con DependencyInjection usando Unity y no me funciona, quisera saber si existe una manera de hacer esto con Unity

    ResponderEliminar