Quartz .NET

Quartz .NET

Introduzione

Quartz è una libreria open source (nata in ambiente Java) che permette di schedulare l’esecuzione del codice scritto all’interno di classi, chiamate job, in modo semplice ma allo stesso tempo con un livello di personalizzazione molto elevato.  I job sono classi .NET standard che implementano l’interfaccia IJob della libreria. La schedulazione dei job avviene tramite i trigger che offrono funzioni per la configurazione delle condizioni sotto cui i job stessi devono essere eseguiti.

Caratteristiche

Come detto, la schedulazione (tramite i trigger) aiuta nella definizione del momento in cui un certo job deve essere eseguito e con quale ricorrenza. I quartz scheduler si occupano infine di associare a ciascun trigger un job. L’esecuzione dei job avviene all’interno di thread (quartz utilizza un ThreadPool) inizializzati e gestiti dalla libreria; pertanto lo sviluppatore non deve preoccuparsi delle difficoltà insite nell’utilizzo dei thread, né della scrittura del codice per la schedulazione.

Configurazione

Quartz.NET è una libreria molto configurabile e vengono fornite tre vie per la configurazione:
·          Programmaticamente usando una NameValueCollection
·          Utilizzando un file chiave/valore di nome quartz.config
·          Utilizzando il classico app.config con la sezione <quartz-element>
Al momento della scrittura dell’articolo, sulla documentazione on-line (http://www.quartzscheduler.net/documentation) non è ancora presente la descrizione di tutte le proprietà che è possibile configurare. Per fortuna è possibile fare riferimento alla documentazione della contro parte java della libreria (http://www.quartz-scheduler.org/documentation/quartz-2.x/configuration/ConfigMain).
Esempi di proprietà configurabili sono le dimensioni del thread pool, il nome da assegnare ai thread, le modalità di memorizzazione delle informazioni di scheduling, ecc
Esempio di configurazione tramite quartz.config:
quartz.scheduler.instanceName = MyScheduler
quartz.threadPool.threadCount = 3
La stessa configurazione la possiamo ottenere programmaticamente in questo modo:
NameValueCollection properties = new NameValueCollection();
properties["quartz.scheduler.instanceName"] = "MyScheduler";
properties["quartz.threadPool.threadCount"] = 3;
NameValueCollection properties = new NameValueCollection();
properties["quartz.scheduler.instanceName"] = "MyScheduler";
properties["quartz.threadPool.threadCount"] = "3";
var factory = new StdSchedulerFactory(properties);
IScheduler scheduler = factory.GetScheduler();

Un semplice esempio

Iniziamo con un esempio che ci permetterà di analizzare i principali elementi della libreria. Innanzitutto creiamo un job, una classe, come detto, che implementi l’interfaccia IJob e definiamo il metodo Execute.
    /// <summary>
    /// Un semplice Job (implementa IJob)
    /// </summary>
    public class FirstJob : IJob
    {
        /// <summary>
        /// Implementazione del metodo che verrà invocato quando le condizioni
        /// definite nel trigger saranno soddisfatte
        /// </summary>
        /// <param name="context"></param>
        public void Execute(IJobExecutionContext context)
        {
            Console.WriteLine("{0} job in execution", GetType().FullName);
             // codice custom
        }
    }
All’interno del metodo Execute è possibile eseguire il nostro codice. E’ questo il metodo che chiamerà il trigger al momento opportuno. Vediamo ora come si configura un trigger e gli si associa il job appena definito.
class Program
{
        static void Main(string[] args)
        {
            try
            {
               
                // Recupero una istanza dello scheduler dalla Factory
                IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
                // lo scheduler viene avviato
                scheduler.Start();
                // costruisco un’istanza di IJobDetail e la lego al mio FirstJob
                IJobDetail job = JobBuilder.Create<FirstJob>().WithIdentity("firstjob", "test").Build();
                // Dico al trigger di far partire subito il job e di rieseguirlo ogni 10 secondi
                ITrigger trigger = TriggerBuilder.Create()
                    .WithIdentity("trigger1", " test")
                    .StartNow()
                    .WithSimpleSchedule(x => x
                        .WithIntervalInSeconds(10).RepeatForever()).Build();
                // Associo il job al trigger
                scheduler.ScheduleJob(job, trigger);
                // questa sleep mi permette di vedere qualcosa sulla console
                Thread.Sleep(TimeSpan.FromSeconds(60));
                // alla fine fermo lo scheduler prima di uscire dall’applicazione
                scheduler.Shutdown();
            }
            catch (SchedulerException se)
            {
                Console.WriteLine(se);
            }
            Console.WriteLine("Press any key to close the application");
            Console.ReadKey();
        }
    }
Innanzitutto è necessario repuperare un’istanza dello scheduler tramite la factory e su questa chiamare il metodo Start; a seguito di questa chiamata quartz inizierà a gestire (attraverso i cosidetti daemon threads) i trigger ed i job che verranno configurati fino a quando non verrà chiamato il metodo Shutdown.  
Utilizzando il metodo statico Create della classe JobBuilder è facile creare un’istanza di IJobDetail associando ad essa il job che abbiamo precedentemente definito. Al job dobbiamo associare un nome e facoltativamente anche un gruppo di appartenenza. Il gruppo permette allo sviluppatore di dare delle etichette sotto le quali organizzare in modo più chiaro ed ordinato i vari job.

JobDataMap

Durante la creazione dei nostri job possiamo fornirgli dei parametri che saranno disponibili durante l’esecuzione dei job stessi all’interno del metodo Execute. A tal fine la libreria espone la JobDataMap, un’implementazione di IDictionary, i cui elementi devono essere serializzabili.
// definisco il job precedente passando dei parametri nella mappa
IJobDetail job = JobBuilder.Create<FirstJob>()
    .WithIdentity("firstjob", "test")
    .UsingJobData("stringa", "Hello World!")
    .UsingJobData("valorefloat", 3.141f)
    .Build();
Il codice precedente mostra quanto sia semplice aggiungere elementi alla JobDataMap.
public void Execute(IJobExecutionContext context)
{
     Console.WriteLine("{0} job in execution", GetType().FullName);
     JobDataMap dataMap = context.MergedJobDataMap;  
     string jobSays = dataMap.GetString("jobSays");
     float myFloatValue = dataMap.GetFloat("myFloatValue");
     // codice custom del nostro job
}
L’esempio dimostra come recuperare i valori dalla mappa, definita in fase di creazione del job. La mappa si trova all’interno dell’istanza di IJobExecutionContext che ci viene fornita come parametro in ingresso del metodo Execute.

Conclusioni

La libreria si presta ad essere utilizzata sia in scenari enterprise (è tranquillamente in grado di gestire centinaia di job) che (data la semplicità di configurazione ed utilizzo) per piccole applicazioni in cui sia necessario l’esecuzione di routine ad intervalli regolari. Infine, la documentazione on-line e gli esempi forniscono tutto il materiale necessario alla schedulazione secondo le esigenze di ciascuna applicazione.

Commenti

Post popolari in questo blog

Azure Service Bus Topics

R – regressione lineare semplice

Polly.NET