web-dev-qa-db-fra.com

Comment fonctionne un écouteur d'événements?

Au cours de l'une de mes conférences d'aujourd'hui sur Unity, nous avons discuté de la mise à jour de la position de notre lecteur en vérifiant chaque image si l'utilisateur a un bouton enfoncé. Quelqu'un a dit que c'était inefficace et que nous devrions plutôt utiliser un écouteur d'événements.

Ma question est, quel que soit le langage de programmation ou la situation dans laquelle il est appliqué, comment fonctionne un écouteur d'événements?

Mon intuition supposerait que l'auditeur d'événements vérifie constamment si l'événement a été déclenché, ce qui signifie, dans mon scénario, que ce ne serait pas différent de vérifier chaque image si l'événement a été déclenché.

Sur la base de la discussion en classe, il semble que l'écouteur d'événements fonctionne d'une manière différente.

Comment fonctionne un écouteur d'événements?

128
Gary Holiday

Contrairement à l'exemple d'interrogation que vous avez fourni (où le bouton est vérifié à chaque image), un écouteur d'événements ne vérifie pas si le bouton est enfoncé du tout. Au lieu de cela, il est appelé lorsque le bouton est enfoncé.

Peut-être que le terme "écouteur d'événements" vous lance. Ce terme suggère que "l'auditeur" fait activement quelque chose à écouter, alors qu'en fait, il ne fait rien du tout. L '"écouteur" est simplement une fonction ou une méthode qui est abonnée à l'événement. Lorsque l'événement se déclenche, la méthode d'écoute ("gestionnaire d'événements") est appelée.

L'avantage du modèle d'événement est qu'il n'y a aucun coût tant que le bouton n'est pas enfoncé. L'événement peut être géré de cette manière sans être surveillé car il provient de ce que nous appelons une "interruption matérielle", qui prévient brièvement le code en cours d'exécution pour déclencher l'événement.

Certaines interfaces utilisateur et cadres de jeu utilisent ce que l'on appelle une "boucle de message", qui met les événements en file d'attente pour une exécution à une période ultérieure (généralement courte), mais vous avez toujours besoin d'une interruption matérielle pour obtenir cet événement dans la boucle de message en premier lieu.

143
Robert Harvey

Un écouteur d'événement s'apparente à un abonnement à la newsletter par e-mail (vous vous enregistrez pour recevoir des mises à jour, dont la transmission est ensuite initiée par l'expéditeur), plutôt que d'actualiser sans cesse une page Web (où vous êtes celui qui initie le transfert d'informations).

Un système d'événements est implémenté à l'aide d'objets d'événements, qui gèrent une liste d'abonnés. Les objets intéressés (appelés abonnés, écouteurs, délégués, etc.) peuvent s'abonner pour être informés d'un événement en appelant une méthode qui s'abonne à l'événement, ce qui oblige l'événement à les ajouter à sa liste. Chaque fois que l'événement est déclenché (la terminologie peut également inclure: appelé, déclenché, invoqué, exécuté, etc.), il appelle la méthode appropriée sur chacun des abonnés, pour les informer de l'événement, en leur transmettant les informations contextuelles dont ils ont besoin pour comprendre ce qui s'est passé.

La réponse courte et insatisfaisante est que l'application reçoit un signal (l'événement) et que la routine n'est appelée qu'à ce point.

L'explication plus longue est un peu plus complexe.

D'où viennent les événements clients?

Chaque application moderne possède une "boucle d'événements" interne, généralement semi-cachée, qui distribue les événements aux composants appropriés qui devraient les recevoir. Par exemple, un événement "clic" est envoyé au bouton dont la surface est visible aux coordonnées actuelles de la souris. C'est au niveau le plus simple. En réalité, le système d'exploitation effectue une grande partie de cette répartition car certains événements et certains composants recevront directement des messages.

D'où viennent les événements d'application?

Les systèmes d'exploitation distribuent les événements lorsqu'ils se produisent. Ils le font de manière réactive en étant avertis par leurs propres chauffeurs.

Comment les pilotes génèrent-ils des événements?

Je ne suis pas un expert, mais à coup sûr certains utilisent des interruptions CPU: le matériel qu'ils contrôlent soulève une broche sur le CPU lorsque de nouvelles données sont disponibles; le CPU déclenche le pilote qui gère les données entrantes, ce qui génère finalement une (file d'attente) d'événements à distribuer, puis renvoie le contrôle au système d'exploitation.

Donc, comme vous le voyez, votre application ne fonctionne pas vraiment du tout. C'est un tas de procédures qui sont déclenchées par le système d'exploitation (sorta) à mesure que les événements se produisent, mais ne font rien le reste du temps.


 il y a des exceptions notables, par exemple jeux pour une fois qui pourraient faire les choses différemment

38
Sklivvz

Terminologie

  • événement: Un type de chose qui peut arriver.

  • déclenchement d'événement: occurrence spécifique d'un événement; un événement qui se passe.

  • écouteur d'événements: Quelque chose qui recherche les déclenchements d'événements.

  • gestionnaire d'événements: Quelque chose qui se produit lorsqu'un écouteur d'événement détecte un déclenchement d'événement.

  • abonné à l'événement: réponse que le gestionnaire d'événements est censé appeler.

Ces définitions ne dépendent pas de l'implémentation, elles peuvent donc être implémentées de différentes manières.

Certains de ces termes sont souvent confondus avec des synonymes, car les utilisateurs n'ont souvent pas besoin de les distinguer.

Scénarios courants

  1. Événements de logique de programmation.

    • L'événement correspond à l'appel d'une méthode.

    • Un déclenchement d'événement est un appel particulier à cette méthode.

    • Le écouteur d'événement est un hook dans la méthode d'événement qui est appelée à chaque déclenchement d'événement qui appelle le gestionnaire d'événement.

    • Le gestionnaire d'événements appelle une collection d'abonnés aux événements.

    • Le abonné (s) à l'événement exécute les actions que le système veut réaliser en réponse à l'occurrence de l'événement.

  2. Événements externes.

    • L'événement est un événement externe qui peut être déduit des observables.

    • Un déclenchement d'événement est lorsque cet événement externe peut être reconnu comme ayant eu lieu.

    • Le écouteur d'événements détecte en quelque sorte les déclenchements d'événements, souvent en interrogeant les observables, puis il appelle le gestionnaire d'événements lors de la détection d'un déclenchement d'événement.

    • Le gestionnaire d'événements appelle une collection d'abonnés aux événements.

    • Le abonné (s) à l'événement exécute les actions que le système veut réaliser en réponse à l'occurrence de l'événement.

Sondage vs insertion de crochets dans le mécanisme de tir de l'événement

Le point soulevé par d'autres est que le vote n'est souvent pas nécessaire. Cela est dû au fait que les écouteurs d'événements peuvent être implémentés en faisant automatiquement déclencher les événements par le gestionnaire d'événements, qui est souvent le moyen le plus efficace d'implémenter des choses lorsque les événements sont des occurrences de niveau système.

Par analogie, vous n'avez pas besoin de vérifier votre boîte aux lettres pour le courrier tous les jours si le postier frappe à votre porte et vous remet le courrier directement.

Cependant, les auditeurs d'événements peuvent également travailler en interrogeant. L'interrogation n'a pas nécessairement besoin de vérifier une valeur spécifique ou autre observable; cela peut être plus complexe. Mais, dans l'ensemble, le but du sondage est de déduire quand un événement s'est produit de telle sorte qu'il puisse être répondu.

Par analogie, vous devez vérifier votre boîte aux lettres tous les jours lorsque le postier y dépose simplement du courrier. Vous n'auriez pas à faire ce travail de sondage si vous pouviez demander au postier de frapper à votre porte, mais ce n'est souvent pas une possibilité.

Chaînage de la logique des événements

Dans de nombreux langages de programmation, vous pouvez écrire un événement qui vient d'être appelé lorsqu'une touche du clavier est enfoncée ou à un certain moment. Bien qu'il s'agisse d'événements externes, vous n'avez pas besoin de les interroger. Pourquoi?

C'est parce que le système d'exploitation vous interroge. Par exemple, Windows vérifie les éléments tels que les changements d'état du clavier, et s'il en détecte un, il appellera les abonnés aux événements. Ainsi, lorsque vous vous abonnez à un événement de pression de clavier, vous vous abonnez en fait à un événement qui est lui-même abonné à un événement qui interroge.

Par analogie, disons que vous vivez dans un complexe d'appartements et qu'un postier dépose du courrier dans une zone de réception du courrier commun. Ensuite, un travailleur de type système d'exploitation peut rechercher ce courrier pour tout le monde, en livrant le courrier aux appartements de ceux qui ont reçu quelque chose. Cela évite à tous les autres d'avoir à interroger la zone de réception du courrier.


Mon intuition supposerait que l'auditeur d'événements vérifie constamment si l'événement a été déclenché, ce qui signifie, dans mon scénario, que ce ne serait pas différent de vérifier chaque image si l'événement a été déclenché.

Sur la base de la discussion en classe, il semble que l'écouteur d'événements fonctionne d'une manière différente.

Comment fonctionne un écouteur d'événements?

Comme vous le soupçonnez, un événement peut fonctionne grâce à l'interrogation. Et si un événement est en quelque sorte lié à des événements externes, par exemple une touche du clavier est pressée, puis l'interrogation doit avoir lieu à un moment donné.

Il est également vrai que les événements ne nécessitent pas nécessairement un sondage. Par exemple, si l'événement se produit lorsqu'un bouton est enfoncé, l'écouteur d'événements de ce bouton est une méthode que le framework GUI peut appeler lorsqu'il détermine qu'un clic de souris frappe le bouton. Dans ce cas, l'interrogation devait encore se produire pour que le clic de souris soit détecté, mais l'écouteur de souris est un élément plus passif connecté au mécanisme d'interrogation primitif via le chaînage d'événements.

Mise à jour: sur l'interrogation matérielle de bas niveau

Il s'avère que les périphériques USB et autres protocoles de communication modernes ont un ensemble de protocoles de type réseau plutôt fascinant pour les interactions, permettant aux périphériques d'E/S, notamment les claviers et les souris, de s'engager dans les topologies ad hoc.

Fait intéressant, " interruptions" sont des éléments synchrones assez impératifs, donc ils ne gèrent pas les topologies de réseau ad hoc. Pour résoudre ce problème, " interruptions" ont été généralisés en paquets asynchrones de haute priorité appelés " interrompre les transactions" (dans le contexte de l'USB) ou " interruptions signalées par un message" (dans le contexte de PCI). Ce protocole est décrit dans une spécification USB:

enter image description here

- " Figure 8-31. Bulk/Control/Interrupt OUT Transaction Host State Machine" in "Universal Serial Bus Specification, Revision 2.0" , page imprimée-222 ; PDF-page-250 (2000-04-27)

L'essentiel semble être que les périphériques d'E/S et les composants de communication (comme les concentrateurs USB) agissent essentiellement comme des périphériques réseau. Ainsi, ils envoient des messages, ce qui nécessite d'interroger leurs ports et autres. Cela réduit le besoin de lignes matérielles dédiées.

Les systèmes d'exploitation comme Windows semblent gérer le processus d'interrogation lui-même, par exemple comme décrit dans la documentation MSDN pour le USB_ENDPOINT_DESCRIPTOR's qui décrit comment contrôler la fréquence à laquelle Windows interroge un contrôleur hôte USB pour les messages d'interruption/isochrones:

La valeur bInterval contient l'intervalle d'interrogation pour les points de terminaison d'interruption et isochrones. Pour les autres types de point de terminaison, cette valeur doit être ignorée. Cette valeur reflète la configuration de l'appareil dans le micrologiciel. Les conducteurs ne peuvent pas le changer.

L'intervalle d'interrogation, ainsi que la vitesse du périphérique et le type de contrôleur hôte, déterminent la fréquence à laquelle le pilote doit déclencher une interruption ou un transfert isochrone. La valeur dans bInterval ne représente pas une durée fixe. Il s'agit d'une valeur relative, et la fréquence d'interrogation réelle dépendra également du fait que l'appareil et le contrôleur hôte USB fonctionnent à basse, pleine ou haute vitesse.

- "Structure USB_ENDPOINT_DESCRIPTOR" , Hardware Dev Center, Microsoft

Les protocoles de connexion de moniteur plus récents comme DisplayPort semblent faire de même:

Transport multi-flux (MST)

  • MST (Multi-Stream Transport) ajouté dans DisplayPort Ver.1.2

    • Seul SST (Single-Stream Transport) était disponible dans la version 1.1a
  • MST transporte plusieurs flux A/V sur un seul connecteur

    • Jusqu'à 63 flux; pas "Stream per Lane"

      • Aucune synchronicité n'est supposée parmi les cours d'eau transportés; un flux peut être dans une période de suppression alors que d'autres ne le sont pas
    • Un transport orienté connexion

      • Chemin depuis une source de flux vers un récepteur de flux cible établi via les transactions de message sur AUX CH avant le début d'une transmission de flux

      • Ajout/suppression d'un flux sans affecter les flux restants

enter image description here

-Faire glisser #14 de "Présentation de DisplayPortTM Ver.1.2" (2010-12-06)

Cette abstraction permet certaines fonctionnalités intéressantes, comme l'exécution de 3 moniteurs à partir d'une connexion:

Le transport multi-flux DisplayPort permet également de connecter trois périphériques ou plus ensemble, mais dans le cas contraire, une configuration moins orientée "consommateur": pilotage simultané de plusieurs écrans à partir d'un seul port de sortie.

- "DisplayPort" , Wikipedia

Conceptuellement, le point à retenir est que les mécanismes d'interrogation permettent des communications série plus généralisées, ce qui est génial lorsque vous voulez des fonctionnalités plus générales. Ainsi, le matériel et le système d'exploitation effectuent beaucoup d'interrogations pour le système logique. Ensuite, les consommateurs qui s'abonnent à des événements peuvent profiter de ces détails traités pour eux par le système de niveau inférieur, sans avoir à écrire leurs propres protocoles d'interrogation/de transmission de messages.

En fin de compte, les événements tels que les pressions sur les touches semblent passer par une série d'événements plutôt intéressants avant d'arriver au mécanisme impératif de déclenchement d'événements du niveau logiciel.

19
Nat

Pull vs Push

Il existe deux stratégies principales pour vérifier si un événement s'est produit ou si un état spécifique est atteint. Par exemple, imaginez attendre une livraison importante:

  • Pull: toutes les 10 minutes, descendez dans votre boîte aux lettres et vérifiez si elle a été livrée,
  • Push: dites au livreur de vous appeler quand il fera la livraison.

L'approche pull (également appelée polling) est plus simple: vous pouvez l'implémenter sans aucune fonctionnalité spéciale. En revanche, c'est souvent moins efficace car vous risquez de faire des vérifications supplémentaires sans rien montrer pour eux.

D'un autre côté, l'approche Push est généralement plus efficace: votre code ne s'exécute que lorsqu'il a quelque chose à faire. D'un autre côté, cela nécessite qu'un mécanisme existe pour enregistrer un écouteur/observateur/rappel1.

1  Mon mailman manque généralement d'un tel mécanisme, malheureusement.

8
Matthieu M.

À propos de l'unité en particulier - il n'y a pas d'autre moyen de vérifier l'entrée du joueur que de l'interroger à chaque image. Pour créer un écouteur d'événements, vous auriez toujours besoin d'un objet tel que "système d'événements" ou "gestionnaire d'événements" pour effectuer l'interrogation, de sorte qu'il ne ferait que pousser le problème vers une classe différente.

Certes, une fois que vous avez un gestionnaire d'événements, vous n'avez qu'une seule classe qui interroge l'entrée à chaque image, mais cela ne donne aucun avantage évident en termes de performances, car maintenant cette classe doit parcourir les auditeurs et les appeler, ce qui, selon votre jeu conception (comme dans combien d'auditeurs sont là et à quelle fréquence le joueur utilise l'entrée), pourrait en fait être plus coûteux.

En dehors de tout cela, rappelez-vous la règle d'or - l'optimisation prématurée est la racine de tout mal, ce qui est particulièrement vrai dans les jeux vidéo, où souvent le processus de rendu de chaque image coûte tellement cher, que les petites optimisations de script comme ça sont complètement insignifiants

1
Dunno

Sauf si vous avez un support dans votre OS/Framework qui gère les événements comme le bouton Push ou le débordement de la minuterie ou l'arrivée des messages - vous devrez de toute façon implémenter ce programme d'écoute d'événements en utilisant l'interrogation (quelque part en dessous).

Mais ne vous détournez pas de ce modèle de conception simplement parce que vous ne bénéficiez pas immédiatement d'un avantage en termes de performances. Voici les raisons pour lesquelles vous devriez l'utiliser, que vous ayez ou non un support sous-jacent pour la gestion des événements.

  1. Le code semble plus propre et plus isolé (s'il est correctement mis en œuvre, bien sûr)
  2. Le code basé sur les gestionnaires d'événements est mieux adapté aux modifications (puisque vous ne modifiez normalement que certains des gestionnaires d'événements)
  3. S'il vous arrive de passer à la plate-forme avec un support d'événement sous-jacent - vous pouvez réutiliser vos gestionnaires d'événements existants et simplement vous débarrasser du code d'interrogation.

Conclusion - vous avez eu la chance de participer à la discussion et avez appris une alternative au vote. Cherchez une occasion d'appliquer ce concept dans la pratique et vous apprécierez l'élégance du code.

1
OpalApps

La plupart boucles d'événements sont construites au-dessus d'une primitive de multiplexage d'interrogation fournie par le système d'exploitation. Sous Linux, cette primitive est souvent l'appel système poll (2) (mais peut être l'ancien select). Dans les applications GUI, le serveur d'affichage (par exemple Xorg , ou Wayland ) communique (via un socket (7) = ou pipe (7) ) avec votre application. Lisez aussi à propos de Protocoles et architecture du système X Window .

Ces primitives d'interrogation sont efficaces; en pratique, le noyau réveillerait votre processus lorsqu'une entrée est effectuée (et qu'une interruption est gérée).

Concrètement, votre bibliothèque widget toolkit communique avec votre serveur d'affichage, attend les messages et envoie ces messages à vos widgets. Les bibliothèques de boîtes à outils comme Qt ou GTK sont assez complexes (des millions de lignes de code source). Votre clavier et votre souris sont uniquement gérés par le processus du serveur d'affichage (qui traduit ces entrées en messages d'événement envoyés aux applications clientes).

(Je simplifie; en fait, les choses sont beaucoup plus complexes)

1

Dans un système basé uniquement sur l'interrogation, un sous-système qui pourrait vouloir savoir quand une action particulière se produit devra exécuter du code à chaque fois que cette action pourrait se produire. S'il existe de nombreux sous-systèmes qui auraient chacun besoin de réagir dans les 10 ms d'un événement non nécessairement unique, ils devraient tous vérifier au moins 100 fois/seconde si leur événement s'est produit. Si ces sous-systèmes sont dans des processus de threads différents (ou pire, des processus), cela nécessiterait une commutation dans chaque thread ou processus 100x/seconde.

Si de nombreuses choses que les applications surveillent sont assez similaires, il peut être plus efficace d'avoir un sous-système de surveillance centralisé - peut-être piloté par table - qui peut surveiller beaucoup de choses et observer si certaines d'entre elles ont changé. S'il y a 32 commutateurs, par exemple, une plate-forme peut avoir une fonction pour lire les 32 commutateurs à la fois dans un mot, permettant au code du moniteur de vérifier si des commutateurs ont changé entre les sondages et - sinon - pas s'inquiéter du code qui pourrait les intéresser.

S'il existe de nombreux sous-systèmes qui souhaiteraient être notifiés en cas de changement, le fait d'avoir un sous-système de surveillance dédié avertissant les autres sous-systèmes lorsque des événements se produisent qui les intéressent peut être plus efficace que de demander à chaque sous-système d'interroger ses propres événements. La mise en place d'un sous-système de surveillance dédié dans les cas où personne ne s'intéresse à aucun événement, cependant, représenterait un pur gaspillage de ressources. S'il n'y a que quelques sous-systèmes intéressés par les événements, le coût de leur surveillance des événements qui les intéressent peut être inférieur au coût de la mise en place d'un sous-système de surveillance dédié à usage général, mais le seuil de rentabilité le point variera considérablement entre les différentes plates-formes.

1
supercat

Un écouteur d'événements suit le modèle de publication/abonnement (en tant qu'abonné)

Dans sa forme la plus simple, un objet de publication conserve une liste des instructions des abonnés à exécuter lorsque quelque chose doit être publié.

Il aura une sorte de méthode subscribe(x), où x dépend de la façon dont le gestionnaire d'événements est conçu pour gérer l'événement. Lorsque subscribe (x) est appelé, x est ajouté à la liste des éditeurs des instructions/références des abonnés.

L'éditeur peut contenir tout, une partie ou aucune de la logique de gestion de l'événement. Il peut simplement exiger des références aux abonnés pour les notifier/les transformer avec sa logique spécifiée lorsque l'événement se produit. Il peut ne contenir aucune logique et nécessiter des objets abonnés (méthodes/écouteurs d'événements) capables de gérer l'événement. Il est fort probable qu'il contienne un mélange des deux.

Lorsqu'un événement se produit, l'éditeur répétera et exécutera sa logique pour chaque élément de sa liste d'instructions/références d'abonnés.

Quelle que soit la complexité d'un gestionnaire d'événements, il suit à la base ce modèle simple.

Exemples

Pour un exemple d'écouteur d'événements, vous fournissez une méthode/fonction/instruction/écouteur d'événements à la méthode subscribe () du gestionnaire d'événements. Le gestionnaire d'événements ajoute la méthode à sa liste de rappels d'abonnés. Lorsqu'un événement se produit, le gestionnaire d'événements parcourt sa liste et exécute chaque rappel.

Pour un exemple concret, lorsque vous vous abonnez à la newsletter sur Stack Exchange, une référence à votre profil sera ajoutée à une table de base de données d'abonnés. Lorsqu'il est temps de publier la newsletter, la référence sera utilisée pour remplir un modèle de newsletter et elle sera envoyée à votre email. Dans ce cas, x est simplement une référence à vous, et l'éditeur dispose d'un ensemble d'instructions internes utilisées pour tous les abonnés.

0
Dom

Un écouteur d'événements est comme une oreille attendant un message. Lorsque l'événement se produit, le sous-programme choisi comme écouteur d'événements fonctionne à l'aide des arguments d'événement.

Il y a toujours deux données importantes: le moment où l'événement se produit et l'objet où cet événement se produit. D'autres arguments sont davantage de données sur ce qui s'est passé.

L'écouteur d'événements spécifie la réaction à ce qui se produit.

0
Rafael Marazuela