Un besoin courant est de lancer un traitement régulièrement, par exemple tous les jours à minuit et à midi.

Pour répondre à cette problématique, il faut se poser bien plus de questions que seulement comment exécuter la tâche à l’heure prévue :

  • Quand doit s’exécuter la tâche dans le cas où les machines sont réparties sur plusieurs fuseaux horaires ? Est-ce midi local ou midi au fuseau horaire Zoulou (GMT) ou Wisky (sobre il correspond à GMT-10) ?
  • Que se passe-t-il si l’exécution de la tâche est anormalement longue ?
  • Que se passe-t-il si elle échoue ? Faut-il la relancer ? Et si elle échoue plusieurs fois d’affilée ?
  • La tâche doit-elle s’exécuter alors que la précédente tourne toujours ?
  • Que se passe-t-il si la machine était arrêtée au moment où la tâche devait s’exécuter ?
  • Comment journaliser les erreurs ?
  • Quel compte utilisateur utiliser pour lancer la tâche ? (et penser à la problématique des mots de passe)

Et surement d’autres problématiques auxquelles je ne pense pas. Bref, ça fait tout de même bien plus de choses que seulement lancer une tâche à heure régulière.

Comme on n’aime pas réinventer la roue, la première chose à se dire est qu’un système utilisé par plus d’un milliard d’utilisateur doit bien fournir un moyen de faire quelque chose d’aussi courant. Et comme Windows est plutôt bien fait (bien plus qu’on ne veut bien le dire), il fournit une réponse à ce besoin : le planificateur de tâches (Task Scheduler en anglais).

Task Scheduler

Les premières problématiques sont traitées comme on peut le voir :

new trigger

Pour la journalisation, le Task Scheduler s’occupe de tout et sauvegarde un maximum d’informations : le début de la tâche, les actions effectuées et le résultat de celles-ci, la fin de la tâche, etc.

task scheduler

Pour ce qui est des comptes utilisateurs, là comme toujours, c’est plus compliqué.

compte utilisateur

Le plus simple est de lancer la tâche sous le compte de l’utilisateur courant uniquement s’il est connecté. Microsoft a pensé à l’UAC avec la dernière option “ Run with highest privileges ” permettant d’utiliser le jeton de sécurité non filtré (i. e. avec tous les groupes et privilèges) pour exécuter la tâche.

L’option “ Do not store password ” utilise Service-for-User (une extension de Kerberos RFC 1510) ne permettant pas à la tâche d’accéder à une ressource sur le réseau. De plus l’utilisateur doit avoir le privilège “ Logon as batch job ”

Pour que la tâche puisse accéder aux ressources sur le réseau sans forcément être connecté, il faut sauvegarder le mot de passe. Le problème qui se pose est la gestion de ce mot de passe. On voit parfois des mots de passe inchangés depuis 5 ans, ce qui bien évidemment n’est pas une bonne pratique.

Windows Server 2008 R2 avait introduit les Managed Service Account, comptes pouvant être utilisés pour les services et pour lesquels Windows se débrouille pour gérer le mot de passe. Windows Server 2012 a étendu cette notion avec les Group Managed Service Account, non limité aux services Windows. La procédure pour créer et utiliser ce type de compte pour une tâche planifiée est présentée ici : http://blogs.technet.com/b/askpfeplat/archive/2012/12/17/windows-server-2012-group-managed-service-accounts.aspx

SQL Server

SQL Server fourni également un planificateur de tâches avec des fonctionnalités similaires si ce n’est que la tâche tourne sous le compte de service de l’agent SQL Server. Son but est principalement d’interagir avec le serveur SQL mais il est possible de lancer une commande ou un script PowerShell.

sql server

sql server

Quartz.NET

Quartz.net est le portage de Quartz, bibliothèque Java, en .NET. Cette bibliothèque fournit de nombreuses options permettant toujours d’arriver à ses fins.

Voici un petit exemple :

public class Job : IJob
{
   public void Execute(IJobExecutionContext context)
   {
   Console.WriteLine(DateTime.Now);
   }
}
ISchedulerFactory schedFact = new StdSchedulerFactory();
IScheduler sched = schedFact.GetScheduler();
sched.Start();
 
// Crée la tâche
IJobDetail job = JobBuilder.Create<Job>()
   .WithIdentity("Job", "Demo")
   .Build();
ITrigger trigger = TriggerBuilder.Create()
   .WithIdentity("Trigger", "Demo")
   .StartNow()
   .WithSimpleSchedule(x => x.WithIntervalInSeconds(5)
   .RepeatForever())
   .Build();
sched.ScheduleJob(job, trigger);

ASP.NET Cache

Une solution que je trouve plutôt fun se base sur l’expiration d’un élément dans le cache en ASP.NET :

protected void Application_Start(object sender, EventArgs e)
{
   AddTask("DoStuff", 5);
}
 
private void AddTask(string name, int seconds)
{
   HttpRuntime.Cache.Insert(name, seconds, null, DateTime.Now.AddSeconds(seconds), Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, CacheItemRemoved);
}
 
public void CacheItemRemoved(string k, object v, CacheItemRemovedReason r)
{
   // TODO: tâche à exécuter
   AddTask(k, Convert.ToInt32(v)); // Replanifie la tâche
}

Bien évidemment cette solution est très limitée en termes de fonctionnalité et n’est pas très précise. De plus lancer une tâche dans le même process que le site web est une mauvaise idée. Rien ne nous dit quand le recyclage de l’app pool aura lieu (peut-être au milieu de la tâche :()

Voilà plusieurs façons de lancer une tâche régulièrement. Personnellement j’opte pour les 2 premières car elles ont été pensées de manière à traiter un maximum de cas et sont faciles à configurer.

Ne ratez plus aucune actualité avec la newsletter mensuelle de SoftFluent

Newsletter SoftFluent