Infrastructure d'accès concurrent

L'un des défis majeurs d'un système complexe est de rester réactif pendant que les tâches sont exécutées. Ce défi est encore plus important sur un système extensible, lorsque des composants qui n'ont pas été conçus pour s'exécuter conjointement partagent les mêmes ressources. Le package org.eclipse.core.runtime.jobs gère ce défi en fournissant une infrastructure pour planifier, exécuter et gérer simultanément des opérations exécutées. Cette infrastructure se base sur l'utilisation de tâches pour représenter une unité de travail qui peut s'exécuter de manière asynchrone.

Tâches

La classe Job représente une unité de travail asynchrone qui s'exécute conjointement à d'autres tâches. Pour exécuter une tâche, un plug-in crée une tâche, puis la planifie. Une fois qu'une tâche est planifiée, elle est ajoutée dans une file d'attente de tâches gérée par la plate-forme. La plate-forme utilise une unité d'exécution de planification de tâche de fond pour gérer toutes les tâches en attente. Lorsque l'exécution d'une tâche prend fin, la tâche est supprimée de la file d'attente et la plate-forme détermine la tâche qui sera exécutée ensuite. Lorsqu'une tâche devient active, la plate-forme appelle sa méthode run(). Il est plus facile de présenter les tâches en s'appuyant sur un exemple simple :
   class TrivialJob extends Job {
      public TrivialJob() {
         super("Trivial Job");
      }
      public IStatus run(IProgressMonitor monitor) {
         System.out.println("This is a job");
         return Status.OK_STATUS;
      }
   }
La tâche est créée et planifiée dans le fragment de code suivant :
   TrivialJob job = new TrivialJob();
   System.out.println("About to schedule a job");
   job.schedule();
   System.out.println("Finished scheduling a job");
La sortie de ce programme dépend du temps. En d'autres termes, il n'existe aucun moyen de savoir lorsque la méthode run d'une tâche sera exécutée vis-à-vis de l'unité d'exécution qui a créé la tâche et l'a planifiée. La sortie est :
   Tâche en cours de planification
   Il s'agit d'une tâche
   Planification de la tâche terminée
ou :
   Tâche en cours de planification
   Planification de la tâche terminée
   Il s'agit d'une tâche

Si vous souhaitez vous assurer qu'une tâche est terminée avant de continuer, vous pouvez utiliser la méthode join(). Cette méthode bloque l'appelant tant que la tâche n'est pas terminée ou tant que l'unité d'exécution qui appelle n'est pas interrompue. Réécrivons le fragment de code ci-dessus de manière plus spécifique :

   TrivialJob job = new TrivialJob();
   System.out.println("About to schedule a job");
   job.schedule();
   job.join();
   if (job.getResult().isOk())
      System.out.println("Job completed with success");
   else
      System.out.println("Job did not complete successfully");
En partant du principe que l'appel de la méthode join() n'est pas interrompu, cette méthode permet de renvoyer le résultat suivant :
   Tâche en cours de planification
   Il s'agit d'une tâche
   Tâche terminée

Evidemment, il n'est généralement pas utile de joindre une tâche immédiatement après l'avoir planifiée, car vous obtenez aucun accès concurrent de cette manière. En pareil cas, vous pouvez tout aussi bien effectuer le travail directement à partir de la méthode run de la tâche dans l'unité d'exécution qui appelle. Nous étudierons des exemples par la suite lorsque l'utilisation des jointures deviendra utile.

Le dernier fragment de code utilise également le résultat de la tâche. Le résultat est l'objet IStatus qui est renvoyé à partir de la méthode run() de la tâche. Vous pouvez utiliser ce résultat pour retransmettre des objets nécessaires à partir de la méthode run de la tâche. Le résultat peut également être utilisé pour indiquer un échec (en renvoyant IStatus avec la gravité IStatus.ERROR) ou un abandon (IStatus.CANCEL).

Opérations de tâches communes

Nous avons appris à planifier une tâche et à attendre qu'elle soit terminée, mais bien d'autres aspects liés aux tâches méritent votre attention. Si vous planifiez une tâche et que vous décidez qu'elle n'est plus nécessaire, la tâche peut être interrompue à l'aide de la méthode cancel(). Si la tâche n'a pas commencé son exécution lorsqu'elle est annulée, elle est immédiatement supprimée et ne sera pas exécutée. D'autre part, si la tâche a déjà commencé son exécution, c'est la tâche qui décide de répondre à l'abandon ou non. Lorsque vous tentez d'annuler une tâche, il s'avère pratique d'utiliser la méthode join() pour patienter. Voici une manière courant d'annuler une tâche et de patienter jusqu'à la fin de la tâche avant de poursuivre :

   if (!job.cancel())
      job.join();

Si l'abandon ne prend pas effet immédiatement, la méthode cancel() renvoie la valeur false et l'appelant utilise la méthode join() pour patienter jusqu'à l'abandon effectif de la tâche.

La méthode sleep() offre une possibilité légèrement moins drastique que l'abandon. Là encore, si la tâche n'a pas commencé son exécution, cette méthode entraîne la mise en attente de la tâche pendant une durée indéfinie. La tâche est mémorisée par la plate-forme et un appel de la méthode wakeUp() entraîne l'ajout de la tâche dans la file d'attente où elle finira par être exécutée.

Etats des tâches

Une tâche franchit différent états au cours de son cycle de vie. Les tâches peuvent non seulement être manipulées grâce à une API comme les méthodes cancel() et sleep(), mais aussi voir leur état changer lorsque la plate-forme exécute et termine ces tâches. Les tâches peuvent prendre les états suivants :

Une tâche ne peut être mise en pause que si son état actuel est EN ATTENTE. Si vous activez une tâche en pause, elle repasse à l'état EN ATTENTE. L'annulation d'une tâche la fait revenir à l'état AUCUN.

Si le plug-in doit connaître l'état d'une tâche en particulier, il peut enregistrer un écouteur de modification de tâche qui génère une notification lorsque la tâche progresse dans son cycle de vie. Cette possibilité s'avère utile pour afficher la progression d'une tâche ou pour envoyer des indications sur celle-ci.

Modules d'écoute de modification de tâche

La méthode addJobChangeListener d'une tâche peut être utilisée pour enregistrer un écouteur associé à une tâche en particulier. IJobChangeListener définit le protocole permettant de répondre aux modifications d'état d'une tâche :

Dans tous ces cas, l'écouteur est fourni avec un protocole IJobChangeEvent qui spécifie la tâche qui subit ce changement d'état et le statut final (si la tâche est exécutée).

Remarque : Les tâches définissent également la méthode getState() pour obtenir l'état (relativement) actuel d'une tâche. Cependant, ce résultat n'est pas toujours fiable car les tâches s'exécutent sur une unité d'exécution différente et peuvent changer de nouveau d'état au moment où l'appel est renvoyé. Les modules d'écoute de modification de tâche constituent le mécanisme recommandé pour détecter les modifications d'état d'une tâche.

Gestionnaire de tâches

IJobManager définit le protocole permettant d'utiliser toutes les tâches dans le système. Les plug-ins qui affiche la progression ou l'utilisation de l'infrastructure des tâches peuvent utiliser IJobManager pour effectuer des tâches comme la suspension de toutes les tâches dans le système, la détection de la tâche exécutée ou la réception des commentaires de progression pour une tâche donnée. Le gestionnaire de tâches de la plate-forme peut être obtenue à l'aide de l'API de la plate-forme :

   IJobManager jobMan = Platform.getJobManager();

Les plug-ins concernés par l'état de toutes les tâches du système peuvent enregistrer un écouteur de modification de tâche dans le gestionnaire de tâches plutôt que d'enregistrer des modules d'écoute dans un grand nombre de tâches individuelles.

Familles de tâches

Il est parfois plus facile pour un plug-in d'utiliser un groupe de tâches associées comme unité unique. Cette opération peut être réalisée à l'aide de familles de tâches. Une tâche déclare qu'elle appartient à une certaine famille en supplantant la méthode belongsTo :

   public static final String MY_FAMILY = "myJobFamily";
   ...
   class FamilyJob extends Job {
      ...
      public boolean belongsTo(Object family) {
         return family == MY_FAMILY;
      }
   }
Le protocole IJobManager peut être utilisé pour annuler, joindre, mettre en pause ou détecter toutes les tâches d'une famille :
   IJobManager jobMan = Platform.getJobManager();
   jobMan.cancel(MY_FAMILY);
   jobMan.join(MY_FAMILY, null);

Dans la mesure où les familles de tâches sont représentées à l'aide d'objets arbitraires, vous pouvez stocker l'état intéressant dans la famille de tâches proprement dite et les tâches peuvent créer des objets famille dynamiquement en fonction des besoins. Il est important d'utiliser des objets famille qui sont relativement uniques pour éviter une interaction accidentelle avec les familles créées par d'autres plug-ins.

Les familles s'avèrent également pratiques pour rechercher des groupes de tâches. La méthode IJobManager.find(Object family) peut être utilisée pour recherche des instances de toutes les tâches en cours d'exécution, en attente, en pause à un moment donné.