Article de reference

boucle d'événements

En informatique , une boucle d'événements est un algorithme qui gère en continu le flux de contrôle en fonction des événements . La boucle demande l'événement suivant à un fourn...

En informatique , une boucle d'événements est un algorithme qui gère en continu le flux de contrôle en fonction des événements . La boucle demande l'événement suivant à un fournisseur d'événements (qui la bloque généralement jusqu'à la survenue d'un événement) et, lorsqu'un événement est reçu, elle appelle son gestionnaire d'événements associé . Lorsque la boucle d'événements est le principal système de gestion des événements d'un programme , comme c'est souvent le cas, on l'appelle boucle principale ou boucle d'événements principale .

Dans les environnements modernes tels que les navigateurs web et les serveurs d'exécution, la boucle d'événements est un mécanisme fondamental qui permet l'exécution asynchrone en surveillant et en distribuant en continu les événements ou messages d'une file d'attente lorsque le thread principal du programme est inactif. En JavaScript, la boucle d'événements permet une gestion non bloquante des tâches telles que les interactions utilisateur, les minuteurs et les opérations d'entrée/sortie, malgré le fait que le langage soit monothread.

Le même algorithme peut être utilisé pour traiter les messages entrants , un sur-ensemble des événements. Dans ce contexte, l'algorithme est appelé boucle de messages , répartiteur de messages ou pompe à messages . Une utilisation courante d'une boucle de messages est la communication interprocessus par échange de messages, où la file d'attente des messages est gérée en dehors du programme (par exemple par le système d'exploitation ).

En règle générale, un programme fonctionnant dans un environnement d'interface utilisateur graphique (GUI) utilise une boucle d'événements, et en raison de la prédominance des environnements GUI, la plupart des applications modernes possèdent une boucle d'événements principale.

Dans le pseudocode suivant, get_next_message()représente une fonction généralement fournie par le système d'exploitation qui bloque l'exécution jusqu'à la réception d'un message. Ainsi, la boucle ne se répète que lorsqu'un message est disponible.

boucle message := obtenir_message_suivant() traiter_message(message) tant que le message n'est pas égal à quitter
de l'utilisateur ). Souvent, ce type de programme possède des comportements optionnels configurés avant son lancement, par exemple via l' interface de ligne de commande (CLI) ou des variables d'environnement . Les logiciels utilitaires sont fréquemment de cette nature.

Même si un programme permet l'interaction avec l'utilisateur, il n'utilise pas forcément de boucle d'événements. Un programme doté d'une interface en ligne de commande (CLI) interne inclut un processeur de commandes similaire à une boucle d'événements, qui distribue les commandes aux gestionnaires en fonction des entrées de l'utilisateur.

Exemples

HTML/JavaScript

Une page web et son code JavaScript s'exécutent généralement dans un processus de navigateur web monothread . Ce processus traite les messages d'une file d'attente un par un. Une fonction JavaScript ou un autre événement du navigateur peut être associé à un message donné. Une fois le traitement d'un message terminé, le processus passe au suivant dans la file d'attente.

Dans les environnements JavaScript, la boucle d'événements utilise des files d'attente distinctes — notamment une file d'attente de tâches et une file d'attente de microtâches — pour gérer les opérations asynchrones telles que les minuteurs, les rappels de promesses et les événements DOM. Lorsque la pile d'appels est vide, la boucle d'événements sélectionne la prochaine tâche à exécuter dans ces files d'attente.

Applications Windows

Sous Windows , un processus interagissant avec l'utilisateur doit accepter les messages entrants et y répondre, ce qui est presque systématiquement assuré par une boucle de messages au sein de ce processus. Sous Windows, un message correspond à un événement créé et imposé au système d'exploitation. Un événement peut être une interaction utilisateur, du trafic réseau, un traitement système, une activité de minuterie, une communication interprocessus, etc. Pour les événements non interactifs, liés uniquement aux entrées/sorties, Windows utilise des ports d'achèvement d'E/S . Les boucles de ces ports s'exécutent indépendamment de la boucle de messages et n'interagissent pas avec elle par défaut.

Le cœur de la plupart des applications Win32 est la fonction WinMain() , qui appelle GetMessage() en boucle. GetMessage() bloque l'exécution jusqu'à la réception d'un message (événement) (la fonction PeekMessage() offre une alternative non bloquante). Après un traitement optionnel, elle appelle DispatchMessage() , qui distribue le message au gestionnaire approprié, également appelé WindowProc . Normalement, les messages sans WindowProc() spécifique sont distribués à DefWindowProc , le gestionnaire par défaut. DispatchMessage() appelle le WindowProc du handle HWND du message (enregistré avec la fonction RegisterClass() ).

Les versions récentes de Windows garantissent que les messages seront acheminés vers la boucle de messages d'une application dans l'ordre où ils ont été perçus par le système et ses périphériques. Cette garantie est essentielle pour la conception d' applications multithread . Cependant, certains messages suivent des règles différentes, comme ceux qui sont toujours reçus en dernier ou ceux dont la priorité est documentée différemment.

Boucle d'événements Xlib

Les applications X utilisant directement Xlib sont construites autour de la XNextEventfamille de fonctions ; XNextEventelles bloquent l’exécution jusqu’à ce qu’un événement apparaisse dans la file d’attente, après quoi l’application le traite de manière appropriée. La boucle d’événements Xlib ne gère que les événements du système de fenêtrage ; les applications qui doivent pouvoir attendre d’autres fichiers et périphériques peuvent construire leur propre boucle d’événements à partir de primitives telles que `std::get_événements` ConnectionNumber, mais en pratique, elles ont tendance à utiliser le multithreading .

Très peu de programmes utilisent directement Xlib. Le plus souvent, les boîtes à outils d'interface graphique basées sur Xlib prennent généralement en charge l'ajout d'événements. Par exemple, les boîtes à outils basées sur Xt Intrinsics possèdent les fonctions `and` XtAppAddInput()et `or` XtAppAddTimeout().

Il n'est pas sûr d'appeler des fonctions Xlib depuis un gestionnaire de signaux, car l'application X peut avoir été interrompue dans un état arbitraire, par exemple pendant une interruption XNextEvent. Voirpour une solution pour X11R5, X11R6 et Xt.

boucle d'événements GLib

La boucle d'événements GLib a été initialement conçue pour GTK , mais elle est désormais utilisée dans des applications non graphiques, telles que D-Bus . La ressource interrogée est l'ensemble des descripteurs de fichiers qui intéressent l'application ; le bloc d'interrogation est interrompu à la réception d'un signal ou à l'expiration d'un délai (par exemple, si l'application a spécifié un délai d'attente ou une tâche inactive). Bien que GLib prenne en charge nativement les événements de fermeture de descripteurs de fichiers et de processus enfants, il est possible d'ajouter une source d'événements pour tout événement pouvant être géré selon un modèle de préparation-vérification-distribution.

Les bibliothèques d'applications basées sur la boucle d'événements GLib incluent GStreamer et les méthodes d'E/S asynchrones de GnomeVFS , mais GTK demeure la bibliothèque cliente la plus visible. Les événements provenant du système de fenêtrage (sous X , lus depuis le socket X ) sont traduits par GDK en événements GTK et émis sous forme de signaux GLib sur les objets widgets de l'application.

macOS Core Foundation exécute des boucles

Une seule boucle CFRunLoop est autorisée par thread, et un nombre arbitraire de sources et d'observateurs peut y être associé. Les sources communiquent ensuite avec les observateurs via la boucle d'exécution, celle-ci gérant la mise en file d'attente et la distribution des messages.

Le CFRunLoop est abstrait dans Cocoa sous la forme d'un NSRunLoop, ce qui permet à tout message (équivalent à un appel de fonction dans les environnements d'exécution non réflexifs ) d'être mis en file d'attente pour être distribué à n'importe quel objet.

Basé sur les fichiers

Sous Unix , le paradigme « tout est fichier » induit une boucle d'événements basée sur les fichiers. La lecture et l'écriture de fichiers, la communication interprocessus, la communication réseau et le contrôle des périphériques sont assurés par les entrées/sorties de fichiers, la cible étant identifiée par un descripteur de fichier . Les appels système `select` et `poll` permettent de surveiller un ensemble de descripteurs de fichiers afin de détecter un changement d'état, par exemple lorsque des données deviennent disponibles en lecture.

Par exemple, prenons un programme qui lit un fichier mis à jour en continu et affiche son contenu dans le système X Window , lequel communique avec les clients via un socket ( domaine Unix ou Berkeley ) :

) x_fd = open_display () construct_interface () while True : rlist , _ , _ = select.select ([ file_fd , x_fd ], [ ] , []): if file_fd in rlist : data = file_fd.read ( ) append_to_display ( data ) send_repaint_message () if x_fd in rlist : process_x_messages ( )

Basé sur le signal

Sous Unix, un signal est un événement asynchrone traité par un gestionnaire de signaux qui s'exécute pendant que le reste de la tâche est suspendu. Si un signal est reçu et traité pendant que la tâche est bloquante select(), `select` retournera prématurément avec `EINTR` . Si un signal est reçu pendant que la tâche est liée au processeur , la tâche sera suspendue entre les instructions jusqu'au retour du gestionnaire de signaux.

Une méthode pour gérer un signal consiste à ce que les gestionnaires de signaux définissent un indicateur global et que la boucle d'événements vérifie sa présence juste avant et après l' select()appel ; si l'indicateur est présent, le signal est traité de la même manière que les événements liés aux descripteurs de fichiers. Malheureusement, cette méthode engendre une condition de concurrence : si un signal arrive immédiatement entre la vérification de l'indicateur et l'appel select(), il ne sera traité que lorsque select()la fonction se terminera pour une autre raison (par exemple, une interruption par un utilisateur impatient).

La solution proposée par POSIX est l' pselect()appel `signal_mask`, similaire à ` signal_mask` select()mais prenant un paramètre supplémentaire sigmaskdécrivant un masque de signal . Ceci permet à une application de masquer les signaux dans la tâche principale, puis de supprimer le masque pendant la durée de l' select()appel, de sorte que les gestionnaires de signaux ne soient appelés que lorsque l'application est limitée par les E/S . Cependant, les implémentations de ` signal_mask` pselect()n'ont pas toujours été fiables ; les versions de Linux antérieures à 2.6.16 ne disposent pas d'un pselect()appel système `signal_mask` , obligeant glibc à l'émuler via une méthode sujette à la même condition de concurrence pselect()que celle-ci vise précisément à éviter.

Une solution alternative, plus portable, consiste à convertir les événements asynchrones en événements basés sur des fichiers en utilisant l' astuce du tube auto-contrôlé , où « un gestionnaire de signal écrit un octet dans un tube dont l'autre extrémité est surveillée par select()le programme principal ». Dans la version 2.6.22 du noyau Linuxsignalfd() , un nouvel appel système a été ajouté, permettant de recevoir des signaux via un descripteur de fichier spécial.

JavaScript et l'exécution asynchrone

Dans des environnements tels que les navigateurs web et Node.js, la boucle d'événements est essentielle au comportement asynchrone des programmes. JavaScript s'exécute sur un seul thread, mais les environnements hôtes fournissent des API pour les opérations non bloquantes, comme les minuteurs, les requêtes réseau et les événements utilisateur. Les opérations asynchrones terminées sont placées dans des files d'attente, et la boucle d'événements vérifie régulièrement si la pile d'appels est vide afin de planifier les rappels en attente. Cela permet aux applications JavaScript de rester réactives tout en gérant plusieurs tâches asynchrones.