Ordonnancement de tâches dans GWT avec la classe Scheduler

| Grégory Le Bonniec (Zenika) | Zenika |

GWT possède, depuis la version 2.0, une classe Scheduler qui fournit un ensemble de méthodes de planification de tâches et de gestion de l'event loop du navigateur. Nous allons voir dans cet article les possibilités offertes par cette classe.

Pour réagir au contenu de cet article, un espace de dialogue vous est proposé sur le forum Commentez .

- Titre : Ordonnancement de tâches dans GWT avec la classe Scheduler

- Auteurs : Grégory Le Bonniec (Zenika), Zenika

- Parution : 29 mars 2012

- Licence : Copyright ® 2012 Grégory Le Bonniec. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Planification de tâches

- I-A. ScheduleFixedDelay & ScheduleFixedPeriod

- I-B. ScheduleEntry & ScheduleFinally

- II. Gestion de l'Event Loop

- II-A. ScheduleIncremental

- II-B. ScheduleDeferred

- III. Conclusion

- IV. Remerciements

I - Planification de tâches

I-A - ScheduleFixedDelay & ScheduleFixedPeriod

Ces deux méthodes permettent d'exécuter une tâche répétitive à intervalles fixes. ScheduleFixedDelay permet d'assurer un temps fixe entre la fin d'une tâche et le début d'une autre (exemple : si une tâche prend 30 ms pour s'exécuter et si un délai de 100 ms est fixé, la tâche suivante s'exécutera 130 ms après l'exécution de la première). ScheduleFixedPeriod permet, elle, d'assurer un temps fixe entre l'ensemble des invocations (exemple : si une tâche prend 30 ms pour s'exécuter et si un délai de 100 ms est fixé, la tâche suivante s'exécutera 100 ms après l'exécution de la première).

Remarque : ces deux méthodes prennent (en plus du délai/période fixé) un objet Scheduler.RepeatingCommand en paramètre qui est exécuté tant qu'il renvoie la valeur true en sortie.

I-B - ScheduleEntry & ScheduleFinally

Afin d'appréhender ces deux méthodes, il est nécessaire de comprendre les mécanismes de base de GWT de traitement des évènements natifs et applicatifs. Il faut tout d'abord savoir que l'ensemble des évènements du navigateur sont interceptés par GWT (mouse, keyboard, load, xhr, timer...) même si le développeur n'estime pas en avoir besoin (i.e. pas d'EventHandler associé).

Dès qu'un événement est intercepté, ce dernier est redispatché vers l'application. Le renvoi de l'évènement est "encadré" par une méthode $entry (méthode proxy). L'objectif principal de cette fonction est de "décorer" tout appel à du code GWT (handler, RPC, timer et load pour faire simple) afin d'assurer la gestion des tâches de type "entry" et "finally".

Premièrement, la méthode $entry dépile l'ensemble des commandes entry en cours et les exécute. Une fois cette étape terminée, $entry lance le code GWT cible. Finalement, $entry exécute l'ensemble des commandes finally en attente.

Pour résumer, une méthode créant des commandes "entry" et "finally" aura pour conséquence l'exécution : - des commandes finally une fois le code GWT en cours exécuté - des commandes entry lors du prochain évènement natif ou applicatif.

Exemple : dans l'application suivante, nous détectons les mouvements effectués sur le label principal. Dès que la souris entre sur le composant, le label est modifié et une commande entry est enregistrée. On constate qu'au premier mouvement, la zone est modifiée mais que le popup d'alerte lui n'apparaît pas. Pour ce faire, il suffit de déclencher un évènement quelconque (autre mouvement de souris ou appui sur une touche du clavier par exemple).

public void onModuleLoad() {
final Label label = new Label("MOVE AREA");
label.setStyleName("label");
RootPanel.get().add(label);

label.addMouseMoveHandler(new MouseMoveHandler() {

public void onMouseMove(MouseMoveEvent event) {
label.setText("MOVE DETECTED");
label.setStyleName("move");

Scheduler.get().scheduleEntry(new ScheduledCommand() {
public void execute() {
Window.alert("ENTRY EXECUTED");
}
});
}
});
}

On comprend mieux maintenant pourquoi la Javadoc indique que les commandes entry permettent d'effectuer une action "dès qu'il se passequelque chose" (sic).

II - Gestion de l'Event Loop

Tout navigateur fonctionne sur le principe d'une event loop. Un moteur JavaScript est monothreadé et ne peut donc traiter qu'un événement à la fois et donc de facto une seule action à la fois (d'où l'importance de l'asynchronisme dans JavaScript) : les événements sont positionnés dans une file d'attente (i.e. FIFO) avant d'être traités un à un.

Nativement, JavaScript ne fournit aucune méthode permettant de prendre la main sur l'event loop du navigateur. C'est afin de pallier cette limitation que GWT fournit deux méthodes : scheduleDeferred et scheduleIncremental.

II-A - ScheduleIncremental

Nous avons tous déjà connu cette situation qui consiste à attendre pendant plusieurs secondes l'affichage d'une liste de données volumineuse. En effet, le fonctionnement monothreadé du JavaScript fait qu'il ne peut pas gérer simultanément la construction de l'arbre et son affichage (layout & paint event).

ScheduleIncremental permet de répondre au besoin d'affichage "en temps réel". Le principe est relativement simple : ScheduleIncremental laisse "souffler" à intervalle régulier le moteur JavaScript pour lui permettre de gérer l'affichage de l'arbre en construction. En effet, une commande incrémentale doit, une fois le travail effectué, indiquer si elle doit être relancée ou pas. Si c'est le cas, un timer (i.e. setTimeout) est lancé 1ms plus tard.

1 ms est la période minimale autorisée par les moteurs JavaScript, c'est largement assez pour laisser le temps au navigateur de mettre à jour l'affichage : durant l'exécution de la commande, toute modification de l'arbre engendre une demande de mise à jour de l'affichage (layout event) qui est alors mise en file d'attente au niveau de l'event loop. L'événement timeout arrive donc après la mise à jour de l'affichage permettant ainsi d'alterner construction de l'arbre et mise à jour de l'affichage, comme dans l'exemple suivant :

public void onModuleLoad() {
Scheduler.get().scheduleIncremental(new RepeatingCommand() {
private int count = 0;

public boolean execute() {
RootPanel.get().add(new Label("****"));
count++;
return count < 1000;
}
});
}

Dans l'outil de Google Speed Tracer, on peut voir l'alternance timer/affichage.

Remarque : pour chaque appel, deux timers sont en fait lancés  : Flusher et Rescuer. Le premier exécute la tâche, le deuxième relance éventuellement le flusher en cas d'échec de celui-ci.

II-B - ScheduleDeferred

L'objectif est d'assurer l'exécution d'une tâche une fois l'ensemble des évènements en attente traités alors que scheduleFinally exécute cette tâche avant. Par exemple, si vous voulez effectuer une tâche avant la mise à jour de l'affichage, vous utiliserez scheduleFinally et scheduleDeferred si vous voulez l'exécuter après la mise à jour de l'affichage (exemple : focus sur un champ donné).

Pour ce faire, l'implémentation est assez proche de scheduleIncremental : un timer de 1 ms est lancé permettant ainsi de mettre la tâche en file d'attente au sein de l'Event Loop et donc de lancer la commande une fois les évènements en attente traités.

III - Conclusion

La classe Scheduler est aujourd'hui une boîte à outils indispensable au sein de la plateforme GWT. ScheduleDeferred permet notamment de gérer des problématiques d'affichage assez fines (positionnement du curseur, calage des ascenseurs...). Il est donc important de la garder sous le coude afin de répondre à l'ensemble des nouveaux besoins des utilisateurs Web.

IV - Remerciements

Cet article a été publié avec l'aimable autorisation de la société Zenika, le billet original peut être trouvé sur le blog de Zenika.

Nous tenons à remercier ClaudeLELOUP pour sa relecture attentive de cet article puis djibril et Mickael Baron pour la mise au gabarit du billet original.