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 :
- Le tandem langage / système d'exploitation ne fournit
généralement aucun mécanisme pour gérer les contraintes de temps.
- 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
Michel Beaudouin-Lafon and Philippe Brun.
A taxonomy and evaluation of formalisms for the specification of
interactive systems.
In Proc. Of HCI'95, 1995.
Michel Beaudouin-Lafon and Joëlle Coutaz.
Scoop / rapport de recherche 1994-1995.
Technical report, SCOOP working group, 1995.
Grady Booch.
Object-Oriented Analysis and Design.
Benjamin Cummings, 2nd edition, 1994.
Alan Burns and Andy Wellings.
Concurrency in Ada.
Cambridge Press, 1995.
Stéphane Chatty.
Extending a graphical toolkit for two-handed interaction.
In Proc of ACM UIST'94, 1994.
Joëlle Coutaz.
Interfaces Homme-ordinateur -- Conception et Réalisation.
Dunod, 1990.
Adele Goldberg.
Smalltalk-80 -- The Interactive Programming Environment.
Addison-Wesley Publishing Company, 1984.
Charles A. R. Hoare.
Monitors: An operating system structuring concept.
Comm. ACM, 10:549--557, 1974.
ISO.
Ada 95 reference manual.
Technical report, 1995.
ISO international standard ISO/IEC 8652:1995.
Mark A. Linton, John M. Vlissides, and Paul R. Calder.
Composing user interfaces with InterViews.
IEEE Computer, pages 8--22, February 1989.
Bertrand Meyer.
Object Oriented Software Construction.
International Series in Computer Science. Prentice Hall, 1988.
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.
John K. Ousterhout.
TCL and the TK Toolkit.
Addison-Wesley Publishing Company, 1994.
Bjärne Strøustrup.
The design and evolution of C++.
Addison-Wesley Publishing Company, 1994.
Christophe Tronche, ch@tronche.com