Présenté à IHM'96.

IHM: vers l'utilisation de langages concurrents

Christophe Tronche
LRI, CNRS URA 410, Bâtiment 490
Université de Paris-Sud, F91405 ORSAY Cedex
(+33) 1 69 41 66 25
ch@tronche.com (formerly tronche@lri.fr).

RÉSUMÉ

Dans cet article, nous défendons le point de vue selon lequel les implémenteurs d'interfaces et avant eux les concepteurs d'architectures d'IHM, se sont laissé influencer par les langages de programmation séquentiels, ce qui les a conduit à une vision centralisée des interfaces dont la structure est, de notre point de vue, naturellement concurrente. Nous indiquons pourquoi cette vision centralisée crée plus de problèmes qu'elle n'en résoud, déjà au niveau des interfaces ``classiques'', et rend malaisée la programmation d'interfaces plus modernes (interaction multimodale, interaction à plusieurs dispositifs d'entrée et collecticiel). Nous prétendons également que la prise en compte de la concurrence n'est réaliste que si le langage d'implantation supporte directement la concurrence, contrairement par exemple à un support déporté dans des bibliothèques.

INTRODUCTION

Les interfaces hommes-machines sont des logiciels complexes qui nécessitent des techniques de conception et de programmation avancées. De ce fait, la discipline de l'interaction homme-machine a par le passé plusieurs fois joué un rôle moteur dans l'introduction ou la pénétration de langages de programmation modernes, comme Smalltalk ou C++, ainsi que dans la diffusion des concepts modernes du génie logiciel tels que la décomposition orientée objet. Aujourd'hui, l'interaction homme-machine fait face à de nouveaux défis, de nouvelles applications de complexité encore accrue telles que les collecticiels. Notre opinion est que pour relever ces défis, les chercheurs de l'interaction homme-machine doivent apprendre à maîtriser des techniques encore insuffisamment répandues dans le monde du logiciel en général, et particulièrement celles liées à l'expression et la modélisation de la concurrence.

Afin d'étayer ce point de vue, nous examinons dans une première partie l'influence de la séquentialité sur les IHMs, nous indiquons une approche possible de la concurrence, et pourquoi selon nous la programmation concurrente n'est intéressante en IHM que si le langage de programmation assiste le programmeur dans sa tâche. Enfin nous donnons quelques exemples de traitements courants en IHM exprimés dans le langage concurrent Ada.

Influence de la séquentialité

Toutes les boîtes à outils d'interaction actuelles un peu répandues sont implémentées au-dessus d'un langage de programmation séquentiel, le plus souvent C ou ses variations. C++ a servit à implémenter Interviews [10], plus tard raffiné en Fresco; les composants IlogViews de Ilog, et Motif++, qui n'est rien d'autre que la réécriture de Motif en C++; NextStep a été implémenté en utilisant Objective-C. Le C a été utilisé pour les interfaces les plus anciennes (comme X, Motif et Microsoft Windows/SDK), et d'autres plus récentes, ce qui tendrait à prouver que peu de réflexions sur le meilleur langage d'implémentation d'une interface ont abouti dans les quinze dernières années. Enfin MacApp, la boîte à outils du MacIntosh, a été initialement développée en Pascal, un langage de niveau proche de C, et est maintenant essentiellement utilisée en C. Un des rares succès hors du C est le couple MVC/Smalltalk [7]. Un autre plus récent est celui de Tcl/Tk [13], une régression de quelques dizaines d'années en ce qui concerne le langage...

Bloquage de l'interface (et de l'application)

La nature purement séquentielle des langages dans le style de C conduit à une architecture d'interface stéréotypée, organisée autour d'une unique boucle d'événement. Les boîtes à outils se divisent alors en deux catégories : dans certaines, il est de la responsabilité du programmeur d'appeler régulièrement une routine d'extraction d'événements (boucle écrite par le programmeur). C'est le cas notamment de X, MacApp et Windows. Si le programmeur ne peut faire en sorte que l'extraction des événements soit appelée divers dommages se produiront, par exemple, la file des événements peut déborder, le réaffichage n'est plus effectué, le processus est bloqué (X) voir la machine complète (MacApp, Windows) !

Dans les autres boîtes à outils cependant, une application interactive se décompose en deux phases : dans un premier temps, l'utilisateur décrit dans un style plus ou moins déclaratif les interacteurs et les différents objets qui dépeignent son interface, par exemple une application Motif débute généralement par des centaines, voire des milliers, de déclarations d'objets réactifs et de leurs différents attributs. L'application contient ensuite une instruction qui donne le contrôle à l'interface. Cette dernière communique ensuite avec le reste de l'application en appelant différentes routines par des ``callbacks''. Notons que cette structuration d'une application interactive favorise une interface figée : si des interacteurs peuvent être créés et détruits à tout moment, il n'y a aucune raison de distinguer entre les interacteurs créés ``avant'' et ``après'' un certain point dans la vie de l'application.

Dans un monde séquentiel, la ``callback'' devra s'exécuter dans un laps de temps suffisamment court (laps de temps que l'interface ne spécifie d'ailleurs pas). Si cette prescription n'est pas observée, des problèmes analogues à ceux survenants dans les boîtes à outils évoquées dans le paragraphe précédent se produiront : bloquage du processus, plus de réaffichage, éventuellement bloquage de l'interaction. Par exemple quand une application Windows ou MacIntosh charge un fichier, le bouton ``Cancel'' est souvent inactif simplement parce que l'application ne peut pas reprendre la main pour traiter l'événement tant que l'appel de chargement de fichier est pendant.

En général, il est recommandé aux programmeurs de gérer ce genre de contraintes par eux-mêmes, notamment par les guides de programmation. Cette solution n'est pas satisfaisante pour au moins deux raisons :

  1. Le tandem langage / système d'exploitation ne fournit généralement aucun mécanisme pour gérer les contraintes de temps.

  2. Dans certain cas, le programmeur n'a aucun moyen d'empêcher un bloquage à l'intérieur de la ``callback''. Un exemple est la résolution d'une adresse par un browser Web Unix : lorsque la ``callback'' lançant l'affichage d'une nouvelle page est déclenchée, l'application doit convertir le nom de la machine distante en adresse IP, un appel bloquant en Unix, ce qui fait que la page n'est pas rafraîchie tant que l'adresse n'a pas été résolue. Pendant ce temps, l'utilisateur ne peut rien faire : même s'il réalise que l'adresse est erronée et que la résolution prendra plusieurs dizaines de secondes, il ne peut interrompre l'opération.
    Les deux principes piliers du génie logiciel et de l'interaction homme-machine ont ici été simultanément violés. D'une part un problème d'implémentation (la résolution des adresses est bloquante) ressurgit au niveau de l'application : faute d'abstraction. D'autre part, l'utilisateur n'a pas le contrôle de son application.

Impossibilité d'extension des modèles.

Les boîtes à outils d'interaction déjà mentionnées sont entièrement structurées autour de deux périphériques d'entrée : la souris et le clavier. Ces boîtes à outils sont si profondément marquées par ces deux périphériques qu'il est très difficile de prendre en compte, par exemple, une deuxième souris [5]. Il n'existe pourtant aucune règle sacrée, que ce soit dans les principes de l'interaction homme-machine ou dans les modèles d'architecture (du type Seeheim, Arch ou PAC) selon laquelle les seuls périphériques d'entrée autorisés sont le clavier et la souris ! C'est sans doute pour cette raison que les premiers praticiens de l'interaction homme-machine à avoir réalisé l'importance du parallélisme dans l'interface sont ceux de la multimodalité (cf. par exemple [12]). Notons que la présence d'un seul dispositif de pointage (la souris) favorise la séquentialité : à un instant donné, il n'existe qu'un seul objet actif, celui pointé par la souris. Ceci se retrouve dans MVC par exemple, puisque le contrôleur invoqué est en principe celui de la fenêtre pointée par la souris.

Des boîtes à outils comme X ``trichent'' d'ailleurs avec leur modèle d'événement en entrée pour tenter de présenter un modèle plus abstrait au programmeur. Par exemple, l'appui sur la touche ``SHIFT'' puis sur une lettre correspond stricto sensu à deux événements clavier successifs; cependant X envoie dans l'événement ``appui sur la touche A'' l'état de la touche SHIFT pour éviter au programmeur d'avoir à garder lui-même la trace de l'état de certaines de ces touches ``stratégiques'' du clavier.

Signalons que l'écriture d'applications collecticielles nécessite la prise en compte d'événement provenant de plusieurs sources (réseau notamment), dont la problématique est proche de celle de l'interaction à multiples dispositifs d'entrées.

Impossibilité de prendre en compte et de manipuler correctement des données multimédia au niveau de l'interface

Un flux multimédia est caractérisé par un flot de contrôle privé (une routine doit être régulièrement exécutée pour afficher la prochaine trame ou jouer le prochain échantillon sonore), et des contraintes de temps : la trame ou l'échantillon doivent être joués dans des intervalles de temps précis pour obtenir une reproduction de bonne qualité. Examinons le cas d'un système de vidéoconférence : le système reçoit un flux sonore et un flux vidéo distinct (les contraintes de temps sur l'image et le son ne sont pas les mêmes). L'application doit écouter simultanément le réseau pour les données multimédia entrantes, écouter l'interface quel que soit son mode de communication avec ladite application, et être prête à traiter au plus tôt le prochain échantillon qui sera soit audio, soit vidéo. L'application doit donc maintenir une liste des prochains événements à venir, un dispositif typiquement disponible dans les couches profondes des outils concurrents, qu'ils s'agisse des systèmes d'exploitation ou du noyau exécutif d'un langage de programmation parallèle. La solution qui est adoptée pour le moment, en tous cas dans le monde Unix, est de mettre la communication vidéo dans un autre processus, laissant le système d'exploitation gérer la concurrence. Mais dans ce cas, on perd tout espoir de voir l'interface filtrer les communications, alors que ce filtrage a été identifié comme l'un des points clés d'une interface collecticielle [2].

Mauvaise prise en compte des modèles existants et de la décomposition de l'application.

De nombreux modèles ont été proposés pour les IHM (voir par exemple [1] pour une revue et une classification). Quoi qu'aucun modèle n'ai réussi à s'imposer jusqu'ici, leur intérêt est de présenter une vision à haut niveau, abstraite, de l'interface, sans se préoccuper des problèmes de l'implémentation, ce qui permet d'avoir une vision claire sur tel ou tel aspect de l'interface. Or nombre de ces modèles sont intrinsèquement parallèles, par exemple les réseaux de Pétri, les langages basés sur les événements et les divers langages de spécification parallèle dont l'application à la modélisation des IHM a été proposée (entre autres CSP, Esterel, Squeak, full LOTOS...). PAC [6] est un modèle très flexible qui est également intrinsèquement parallèle : des agents décrivent différents éléments de l'interface, et il n'y a aucune raison pour qu'ils ne fonctionnent pas concurremment. Le modèle de Seeheim semble être plutôt séquentiel, quoique ce ne soit pas explicite. Mais ceci prouve peut-être que ce modèle a été trop influencé par les implémentations déjà existantes à l'époque où il a été conçu.

La modélisation orientée-objet, une famille de méthodes d'analyse / conception parmi les meilleures découvertes jusqu'ici, est particulièrement performante pour les interfaces homme-machine. Ceci est parfaitement naturel, puisque la philosophie de ces méthodes et de modéliser directement les ``objets'' du monde réel par des objets informatiques. Or les interfaces hommes-machines s'appuient sur des métaphores du monde réel (par exemple le bouton), suivant en cela une démarche parallèle à celle des méthodes orientée-objets. Les tenants de ces méthodes souhaitent une modélisation intrinsèquement concurrente [3,11]. En effet, dans le monde réel, les objets ont tendance à fonctionner concurremment. Pour la même raison, les ``interfaces'' du monde réel sont à flux d'entrée multiples : pour lancer un enregistrement avec mon magnétoscope, je dois appuyer simultanément sur ``record'' et ``play''. Il est intéressant de noter que Booch et Meyer indiquent que l'absence de concurrence limite la possibilité de créer des composants réutilisables. Or si les boîtes à outils graphiques sont de bons exemple de réutilisation de code réussie, les exemples de composants réutilisables multimédia ou multi-utilisateurs ayant réussi à trouver une large audience sont extrêmement rares.

La concurrence comme solution

On l'a compris, la prise en compte de la concurrence dans les interfaces est une des étapes que nous devons franchir sur le chemin qui mène aux interfaces du futur. Nous indiquons maintenant sur un exemple pourquoi cette concurrence doit être prise en compte directement dans le langage de programmation.

Une première approche

Remarquons tout d'abord qu'il est nécessaire de verrouiller certains objets pour changer leur état (accès en exclusion mutuelle) afin de garantir leur cohérence. Tous les objets doivent avoir un comportement propre (tous les objets s'exécutent concurremment), car il est impossible de prévoir ceux qui auront besoin effectivement de s'exécuter en parallèle : l'utilisateur a l'initiative.

Si l'on implémente ces mécanismes dans un langage non-parallèle tel que C++, on peut imaginer créer une classe décrivant les objets concurrents qui effectue les appels systèmes nécessaires pour obtenir du système d'exploitation une ``thread'' de calcul. Toutes les classes d'interacteurs hériteront de cette classe pour s'exécuter concurremment. Ceci pourrait s'écrire en C++ :

class concurrentObject {
	// Méthodes de gestion de
	// la concurrence
	void lock();
	void unlock();
	...
};

class interactor: public concurrentObject {
	// Méthodes communes à tous 
	// les interacteurs.
	void redraw();
	void acceptInput(const XEvent &);
	...
};

class pushButton: public interactor {
	// Méthodes spécifique à 
	// l'interacteur pushButton.
	void activate();
	bool enfonce();
	...
};

Un problème : le verrouillage des objets

Le programmeur doit pouvoir choisir une granularité de verrouillage, ce qui est très difficile. Faut il verrouiller au niveau du pixel ? De l'interacteur ? D'un groupe d'interacteurs ? Plus la granularité de verrouillage est importante, plus on se rapproche du cas non concurrent. Quoi qu'il en soit, le programmeur doit insérer à la main une instruction de verrouillage (puisque le langage ne le fait pas lui-même), et une instruction de déverrouillage à la fin de la section critique. Notons que cette méthode a été critiquée notamment par Hoare (dans le monde de la programmation concurrente), ce qui l'a amené à l'introduction de la notion de moniteurs [8].

La solution : un langage qui garantit le verrouillage

Nous devons donc disposer de la concurrence directement dans le langage de programmation. A titre d'exemple, nous donnons maintenant quelques indications sur une méthode possible de programmation d'une toolkit graphique que nous avons commencé à expérimenter, en utilisant comme langage concurrent le langage Ada dans sa dernière forme Ada 95 [9]. Nous avons choisi Ada plutôt que d'autres langages concurrents déjà expérimentés pour la réalisation d'interfaces (comme Emerald ou Guide), car, l'interface n'étant qu'un des composants d'une application, il est crucial d'utiliser un langage suffisamment répandu pour pouvoir trouver des composants déjà utilisables au prix d'un effort raisonnable en même temps que des programmeurs ayant une expertise suffisante.

Commencons par définir la racine des interacteurs :

   protected type INTERACTOR_BEHAVIOUR is
     procedure REDRAW;
     procedure ACCEPT_INPUT(ITEM : in XEVENT);
   end INTERACTOR_BEHAVIOUR;

   type INTERACTOR is abstract 
	tagged limited record
	 OBJECT : INTERACTOR_BEHAVIOUR;
      end record;

A la différence du code C++ précédent, l'interacteur est ici un objet dont tous les accès sont automatiquement verrouillés (un protected record dans le jargon Ada). C'est aussi une classe abstraite au sens de C++. Le PushButton s'écrit :

   protected type PUSH_BUTTON_BEHAVIOUR is
     procedure ACTIVATE;
     function ENFONCE return BOOLEAN;
   end PUSH_BUTTON_BEHAVIOUR;
  
   type PUSH_BUTTON is new INTERACTOR with
      record
        ITEM : PUSH_BUTTON_BEHAVIOUR;
     end record;

Le PUSH_BUTTON hérite des méthodes de INTERACTOR, en l'occurrence les méthodes REDRAW et ACCEPT_INPUT.

Les ``protected records'' de Ada implémentent des moniteurs de Hoare avec la sémantique suivante : les fonctions sont à lecture seule, donc plusieurs agents (par exemple plusieurs utilisateurs) peuvent lire concurremment l'état du bouton (par la fonction ENFONCE). En revanche un seul peut, à un instant donné, modifier l'état du bouton (par la fonction ACTIVATE).

Plusieurs sources d'événements sont possibles, par exemple :

   task SERVER_X;
   task GRAPHIC_TABLET;

   task body SERVER_X is
      E : XEVENT;
   begin -- SERVER_X
      loop
	 GET_NEXT_EVENT(E);
	 DISPATCH_EVENT(E);
      end loop;
   end SERVER_X;

   task body GRAPHIC_TABLET is
      E : TABLET_EVENT;
   begin -- GRAPHIC_TABLET
      loop
	 GET_NEXT_EVENT(E);
	 DISPATCH_EVENT(E);
      end loop;
   end GRAPHIC_TABLET;

Les deux tâches s'exécutent en parallèle. La méthode DISPATCH_EVENT effectue présumément des appels aux méthodes des interacteurs (des appels d'entrées dans le jargon Ada). Si les deux sources d'événements opèrent sur des objets distincts, les opérations se déroulent concurremment. Si les deux tentent d'accéder simultanément au même objet, l'une d'entre elles est mise en attente.

Un langage concurrent riche tel que Ada fournit de nombreux mécanismes de synchronisation, qui peuvent être utilisés pour exprimer facilement des contraintes telles que ``Appuyer simultanément sur les boutons Record et Play''. Pour une revue complète de ces mécanismes, on consultera [4]. Nous nous contenterons d'indiquer ici comment exprimer une contrainte de temps :

   task VIDEO is
      accept VIDEO_FRAME(A_FRAME : in FRAME);
   end task;

   task body VIDEO is
   begin
      loop
	 select
	    accept VIDEO_FRAME
		(A_FRAME : in FRAME) do
	       DISPLAY(A_FRAME);
	    end VIDEO_FRAME;
         then abort
	   delay 0.1;
	 end select;
      end loop;

Dans cet exemple, la tâche obtient et affiche une trame en 0.1 seconde. Si l'opération ne peut être effectuée dans le temps imparti, l'opération est avortée.

Un autre avantage important, outre un verrouillage correct est l'efficacité : le compilateur peut construire un graphe d'utilisation des tâches pour optimiser l'allocation des ``threads'' de calcul, une opération relativement coûteuse. Ceci est pour le moins très difficile si la concurrence est fournie par une bibliothèque.

Notons que si le support du langage pour la concurrence est nécessaire, elle n'est pas suffisante. Le noyau exécutif du langage doit coopérer avec le système d'exploitation pour assurer le bon fonctionnement global. Par exemple, si l'ensemble des tâches Ada est implémenté à l'intérieur d'un seul processus Unix, un appel tel que XNextEvent (extraction du prochain événement de la file) est bloquant et bloque toutes les tâches, et non seulement celle qui exécute l'appel. A ce moment, c'est sans intérêt. Au contraire, si le noyau d'exécution affecte une ``thread'' du système d'exploitation à chaque tâche, seules celles qui sont en attente d'événements sont bloquées, pendant que les autres peuvent continuer à faire agir les autres objets.

CONCLUSION

Les progrès accomplis durant les quinze dernière années en matière de génie logiciel ont plus porté sur les méthodes que sur l'adoption de nouveaux langages de programmation. Le langage roi reste le C, un assembleur portable créé il y a plus de vingt ans. Le progrès le plus marquant de la décennie aura été l'introduction du C++, mais on peut attendre davantage de la part d'un langage de programmation à la fin des années 90. Les méthodes (notamment orientées-objets), ont en revanche fait considérablement progresser notre compréhension du processus de création et de structuration du logiciel. L'interaction homme-machine avec ses logiciels complexes et son besoin en architecture de logiciel ``hétérogène'' (interface + noyau applicatif) a d'ailleurs joué un rôle non négligeable, au moins dans l'acceptation de C++, même si notre discipline a été plus discrète (jusqu'ici) dans le domaine des méthodes. A l'heure où ces méthodes de conception ont tellement progressées et prennent en compte la concurrence (au point que l'introduction de son support dans C++ est considéré comme le seul élément ayant une chance d'être ajouté avant la finalisation du standard [14]), il est temps que l'IHM joue à nouveau un rôle moteur et franchisse le pas vers l'adoption de langages concurrents.

BIBLIOGRAPHIE

  1. Michel Beaudouin-Lafon and Philippe Brun. A taxonomy and evaluation of formalisms for the specification of interactive systems. In Proc. Of HCI'95, 1995.

  2. Michel Beaudouin-Lafon and Joëlle Coutaz. Scoop / rapport de recherche 1994-1995. Technical report, SCOOP working group, 1995.

  3. Grady Booch. Object-Oriented Analysis and Design. Benjamin Cummings, 2nd edition, 1994.

  4. Alan Burns and Andy Wellings. Concurrency in Ada. Cambridge Press, 1995.

  5. Stéphane Chatty. Extending a graphical toolkit for two-handed interaction. In Proc of ACM UIST'94, 1994.

  6. Joëlle Coutaz. Interfaces Homme-ordinateur -- Conception et Réalisation. Dunod, 1990.

  7. Adele Goldberg. Smalltalk-80 -- The Interactive Programming Environment. Addison-Wesley Publishing Company, 1984.

  8. Charles A. R. Hoare. Monitors: An operating system structuring concept. Comm. ACM, 10:549--557, 1974.

  9. ISO. Ada 95 reference manual. Technical report, 1995. ISO international standard ISO/IEC 8652:1995.

  10. Mark A. Linton, John M. Vlissides, and Paul R. Calder. Composing user interfaces with InterViews. IEEE Computer, pages 8--22, February 1989.

  11. Bertrand Meyer. Object Oriented Software Construction. International Series in Computer Science. Prentice Hall, 1988.

  12. Laurence Nigay and Joëlle Coutaz. A design space for multimodal systems: concurrent processing and data fusion. In Proc. Of INTERCHI'93, pages 172--178, 1993.

  13. John K. Ousterhout. TCL and the TK Toolkit. Addison-Wesley Publishing Company, 1994.

  14. Bjärne Strøustrup. The design and evolution of C++. Addison-Wesley Publishing Company, 1994.

Christophe Tronche, ch@tronche.com