Wie funktioniert ein Ereignis-Listener?

125

In einem meiner heutigen Vorträge über Unity haben wir uns mit der Aktualisierung unserer Player-Position befasst, indem wir jedes Bild überprüft haben, wenn der Benutzer eine Taste gedrückt hat. Jemand sagte, dies sei ineffizient und wir sollten stattdessen einen Ereignis-Listener verwenden.

Meine Frage ist, unabhängig von der Programmiersprache oder der Situation, in der sie angewendet wird, wie ein Ereignis-Listener funktioniert.

Meine Intuition würde annehmen, dass der Ereignis-Listener ständig prüft, ob das Ereignis ausgelöst wurde, was bedeutet, dass es in meinem Szenario nicht anders ist, als jeden Frame zu prüfen, wenn das Ereignis ausgelöst wurde.

Aufgrund der Diskussion in der Klasse scheint der Ereignis-Listener anders zu arbeiten.

Wie funktioniert ein Ereignis-Listener?

Gary Holiday
quelle
34
Ein Ereignis-Listener prüft überhaupt nicht. Es wird aufgerufen, wenn das Ereignis "lauscht", um Brände.
Robert Harvey
13
Ja, aber wie "hört" es, würde es nicht ständig nachsehen?
Gary Holiday
28
Nee. "Event Listener" ist wahrscheinlich eine schlechte Wortwahl; es "hört" überhaupt nicht zu. Ein Ereignis-Listener wartet lediglich darauf, vom Ereignis aufgerufen zu werden, wenn es ausgelöst wird, ähnlich wie bei jeder anderen Methode. Bis es auf diese Weise aufgerufen wird, tut es überhaupt nichts.
Robert Harvey
28
Jedes Mal, wenn Sie überprüfen, ob die Taste gedrückt wurde, werden Sie mit Takten belastet. Der Event-Handler (Listener) kostet Sie nur, wenn der Button tatsächlich gedrückt wird.
Robert Harvey
45
@RobertHarvey - nicht unbedingt, da "Listener" immer noch ein konstantes Polling auf niedrigerer Ebene benötigen. Sie müssen nur die Komplexität von Ihrer eigenen Codeschicht auf Hardware-Interrupts oder was auch immer reduzieren. Und ja, dies ist normalerweise effizienter, aber nicht, weil das Abhören dem Abrufen überlegen ist, sondern weil das Abrufen auf niedrigerer Ebene effizienter ist als das Abrufen von C # und 15 Abstraktionsebenen zwischen Ihnen und Hardware.
Davor Ždralo

Antworten:

140

Im Gegensatz zu dem von Ihnen angegebenen Abfragebeispiel (bei dem die Schaltfläche in jedem Frame überprüft wird) prüft ein Ereignis-Listener nicht, ob die Schaltfläche überhaupt gedrückt wurde. Stattdessen wird es aufgerufen, wenn der Knopf gedrückt wird.

Vielleicht wirft der Begriff "Ereignis-Listener" Sie um. Dieser Begriff deutet darauf hin, dass der "Zuhörer" aktiv etwas tut, um zuzuhören, obwohl er überhaupt nichts tut. Der "Listener" ist lediglich eine Funktion oder Methode, die für das Ereignis abonniert ist. Wenn das Ereignis ausgelöst wird, wird die Listener-Methode ("Event-Handler") aufgerufen.

Der Vorteil des Ereignismusters besteht darin, dass keine Kosten anfallen, bis der Knopf tatsächlich gedrückt wird. Das Ereignis kann auf diese Weise behandelt werden, ohne überwacht zu werden, da es von einem sogenannten "Hardware-Interrupt" herrührt, der den ausgeführten Code kurzzeitig veranlasst, das Ereignis auszulösen.

Einige UI- und Game-Frameworks verwenden eine sogenannte "Nachrichtenschleife", die Ereignisse für die Ausführung zu einem späteren (normalerweise kurzen) Zeitpunkt in die Warteschlange stellt. Sie benötigen jedoch immer noch einen Hardware-Interrupt, um dieses Ereignis überhaupt in die Nachrichtenschleife zu bringen.

Robert Harvey
quelle
54
Es kann erwähnenswert sein, warum es keine Kosten gibt, bis die Taste gedrückt wird, weil die Tasten "speziell" sind, der Computer Interrupts und andere spezielle Funktionen hat, die das Betriebssystem verwenden kann und die in Userspace-Anwendungen abstrahiert sind.
Whatsisname
46
@whatsisname während das sehr tief unter der Haube der Fall ist , arbeiten Game Engines in der Praxis wahrscheinlich nicht mit Interrupts, sondern rufen tatsächlich immer noch eine Ereignisquelle in einer Schleife ab. Es ist nur so, dass diese Abfrage zentralisiert und optimiert ist, sodass das Hinzufügen weiterer Ereignis-Listener keine zusätzliche Abfrage und Komplexität bedeutet.
gntskn
7
@PieterGeerkens Ich würde vermuten, dass gntskn bedeutet, dass es im Rahmen der Game-Engine-Schleife einen Schritt gibt, der nach ausstehenden Ereignissen sucht. Ereignisse würden während jeder Schleife zusammen mit allen anderen Einmal-pro-Schleife-Aktivitäten verarbeitet. Es würde keine separate Schleife zum Überprüfen von Ereignissen geben.
Joshua Taylor
2
@Voo: Umso mehr Grund, in diesem Beitrag nicht auf diesen Detaillierungsgrad einzugehen.
Robert Harvey
2
@ Voo: Ich spreche von Tasten wie den physischen Tasten auf der Tastatur und den Maustasten.
Whatsisname
52

Ein Ereignis-Listener, der einem E-Mail-Newsletter-Abonnement ähnelt (Sie registrieren sich, um Updates zu erhalten, deren Übertragung später vom Absender initiiert wird), anstatt eine Webseite endlos zu aktualisieren (wenn Sie derjenige sind, der die Übertragung von Informationen initiiert).

Ein Ereignissystem wird mithilfe von Ereignisobjekten implementiert, die eine Liste von Abonnenten verwalten. Interessierte Objekte ( Abonnenten , Listener , Delegierte usw. genannt) können sich abonnieren, um über ein Ereignis informiert zu werden, indem sie eine Methode aufrufen, die sich selbst für das Ereignis anmeldet, wodurch das Ereignis veranlasst wird, sie zu seiner Liste hinzuzufügen. Jedes Mal, wenn das Ereignis ausgelöst wird (Terminologie kann auch Folgendes umfassen: aufgerufen , ausgelöst , aufgerufen , ausgeführt usw.), ruft sie die entsprechende Methode für jeden der Abonnenten auf, um sie über das Ereignis zu informieren und alle Kontextinformationen weiterzuleiten, die sie verstehen müssen Was ist passiert.

Alexander
quelle
38

Die kurze, unbefriedigende Antwort ist, dass die Anwendung ein Signal (das Ereignis) empfängt und dass die Routine nur an diesem Punkt aufgerufen wird.

Die längere Erklärung ist etwas komplizierter.

Woher kommen Kundenveranstaltungen?

Jede moderne Anwendung verfügt über eine innere, normalerweise versteckte "Ereignisschleife", die Ereignisse an die richtigen Komponenten sendet, die sie empfangen sollen. Beispielsweise wird ein "Klick" -Ereignis an die Schaltfläche gesendet, deren Oberfläche an den aktuellen Mauskoordinaten sichtbar ist. Dies ist auf der einfachsten Ebene. In der Realität übernimmt das Betriebssystem einen Großteil dieses Dispatchings, da einige Ereignisse und einige Komponenten Nachrichten direkt empfangen.

Woher kommen Anwendungsereignisse?

Betriebssysteme lösen Ereignisse aus, sobald sie eintreten. Sie reagieren darauf, indem sie von ihren eigenen Fahrern benachrichtigt werden.

Wie erzeugen Fahrer Ereignisse?

Ich bin kein Experte, aber sicher verwenden einige CPU-Interrupts: Die von ihnen gesteuerte Hardware wirft einen Stift auf die CPU, wenn neue Daten verfügbar sind; Die CPU löst den Treiber aus, der die eingehenden Daten verarbeitet, die schließlich eine (Warteschlange von) Ereignissen erzeugen, die ausgelöst werden sollen, und gibt dann die Steuerung an das Betriebssystem zurück.

Wie Sie sehen, wird Ihre Anwendung nicht immer ausgeführt. Es handelt sich um eine Reihe von Prozeduren, die vom Betriebssystem (sorta) ausgelöst werden, wenn Ereignisse eintreten, aber die restliche Zeit nichts tun.


Es gibt bemerkenswerte Ausnahmen, zB Spiele für einmal, die Dinge anders machen könnten

Sklivvz
quelle
10
Diese Antwort erklärt, warum keine Abfrage für Mausklickereignisse in einem Browser erforderlich ist. Hardware generiert Interrupt => Treiber löst es in OS-Ereignis auf => Browser löst es in DOM-Ereignis auf => JS-Engine führt Listener für dieses Ereignis aus.
Tibos
@ Tibos afaict es gilt auch für Tastatur-Events, Timer-Events, Paint-Events, etc.
Sklivvz
19

Terminologie

  • event : Eine Art von Sache, die passieren kann.

  • Ereignisauslösung : Ein bestimmtes Auftreten eines Ereignisses; ein Ereignis passiert.

  • Ereignis-Listener : Etwas, das auf Ereignisauslösungen achtet.

  • Ereignishandler : Dies tritt auf, wenn ein Ereignis-Listener einen Ereignisauslöser erkennt.

  • Ereignisabonnent : Eine Antwort, die der Ereignishandler aufrufen soll.

Diese Definitionen hängen nicht von der Implementierung ab, sodass sie auf verschiedene Arten implementiert werden können.

Einige dieser Begriffe werden häufig mit Synonymen verwechselt, da Benutzer häufig nicht zwischen ihnen unterscheiden müssen.

Häufige Szenarien

  1. Programmierlogik-Ereignisse.

    • Das Ereignis tritt auf, wenn eine Methode aufgerufen wird.

    • Eine Ereignisauslösung ist ein bestimmter Aufruf dieser Methode.

    • Der Ereignis-Listener ist ein Hook in der Ereignismethode, die bei jedem Ereignisauslöser aufgerufen wird, der den Ereignishandler aufruft.

    • Die Ereignisbehandlungsroutine ruft eine Sammlung von Ereignisabonnenten auf.

    • Die Ereignis-Teilnehmer führen alle Aktionen aus, die das System als Reaktion auf das Ereignis ausführen möchte.

  2. Externe Ereignisse.

    • Das Ereignis ist ein externes Ereignis, das aus Observablen abgeleitet werden kann.

    • Eine Ereignisauslösung ist, wenn dieses externe Ereignis als eingetreten erkannt werden kann.

    • Der Ereignis-Listener erkennt auf irgendeine Weise Ereignisauslösungen, häufig durch Abfragen der Observable (s), und ruft dann die Ereignisbehandlungsroutine auf, wenn er eine Ereignisauslösung erkennt.

    • Die Ereignisbehandlungsroutine ruft eine Sammlung von Ereignisabonnenten auf.

    • Die Ereignis-Teilnehmer führen alle Aktionen aus, die das System als Reaktion auf das Ereignis ausführen möchte.

Polling vs. Einfügen von Hooks in den Auslösemechanismus des Events

Der Punkt, den andere sagen, ist, dass Umfragen oft nicht notwendig sind. Dies liegt daran, dass Ereignis-Listener implementiert werden können, indem Ereignisauslösungen automatisch den Ereignishandler aufrufen. Dies ist häufig die effizienteste Methode, um Ereignisse zu implementieren, die auf Systemebene auftreten.

Analog müssen Sie nicht jeden Tag in Ihrem Briefkasten nach der Post suchen, wenn der Postbote an Ihre Tür klopft und die Post direkt an Sie weitergibt.

Ereignis-Listener können jedoch auch durch Abfragen arbeiten. Bei der Abfrage muss nicht unbedingt ein bestimmter Wert oder eine andere beobachtbare Größe überprüft werden. es kann komplexer sein. Insgesamt besteht der Punkt der Abfrage jedoch darin, zu schließen, wann ein Ereignis aufgetreten ist, so dass darauf reagiert werden kann.

Analog dazu müssen Sie jeden Tag in Ihrem Briefkasten nachsehen, wenn der Postbeamte gerade die Post dort abliefert. Sie müssten diese Umfrage nicht durchführen, wenn Sie den Postangestellten anweisen könnten, an Ihre Tür zu klopfen, aber dies ist häufig nicht möglich.

Verkettung der Ereignislogik

In vielen Programmiersprachen können Sie ein Ereignis schreiben, das nur aufgerufen wird, wenn eine Taste auf der Tastatur gedrückt wird oder zu einer bestimmten Zeit. Obwohl es sich um externe Ereignisse handelt, müssen Sie diese nicht abfragen. Warum?

Es ist, weil das Betriebssystem für Sie abruft. Windows prüft beispielsweise auf Änderungen des Tastaturstatus und ruft Ereignisabonnenten an, wenn eines erkannt wird. Wenn Sie also ein Tastaturdruckereignis abonnieren, abonnieren Sie tatsächlich ein Ereignis, bei dem es sich selbst um einen Abonnenten eines abfragenden Ereignisses handelt.

Nehmen wir analog an, Sie leben in einem Apartmentkomplex und ein Postangestellter schickt die Post in einen Empfangsbereich für Gemeinschaftspost. Dann kann ein Mitarbeiter, der einem Betriebssystem ähnelt, für alle Benutzer nach dieser E-Mail suchen und diese an die Apartments derjenigen senden, die etwas erhalten haben. Dies erspart allen anderen die Notwendigkeit, den Posteingangsbereich abzufragen.


Meine Intuition würde annehmen, dass der Ereignis-Listener ständig prüft, ob das Ereignis ausgelöst wurde, was bedeutet, dass es in meinem Szenario nicht anders ist, als jeden Frame zu prüfen, wenn das Ereignis ausgelöst wurde.

Aufgrund der Diskussion in der Klasse scheint der Ereignis-Listener anders zu arbeiten.

Wie funktioniert ein Ereignis-Listener?

Wie Sie vermutet haben, ein Ereignis kann durch Polling arbeiten. Und wenn ein Ereignis in irgendeiner Weise mit externen Ereignissen zusammenhängt, z. B. dem Drücken einer Tastaturtaste, muss irgendwann ein Abruf stattfinden.

Es ist nur auch wahr, dass Ereignisse nicht unbedingt eine Abstimmung beinhalten müssen. Wenn das Ereignis beispielsweise ein Drücken einer Schaltfläche ist, ist der Ereignis-Listener dieser Schaltfläche eine Methode, die das GUI-Framework möglicherweise aufruft, wenn festgestellt wird, dass ein Mausklick auf die Schaltfläche zutrifft. In diesem Fall musste noch eine Abfrage durchgeführt werden, damit der Mausklick erkannt wurde. Der Maushörer ist jedoch ein passiveres Element, das über die Ereignisverkettung mit dem primitiven Abfragemechanismus verbunden ist.

Update: Bei Low-Level-Hardware-Polling

Es stellt sich heraus, dass USB-Geräte und andere moderne Kommunikationsprotokolle über eine faszinierende Reihe netzwerkähnlicher Protokolle für Interaktionen verfügen, die es E / A-Geräten, einschließlich Tastaturen und Mäusen, ermöglichen, sich an Ad-hoc- Topologien zu beteiligen.

Interessanterweise sind " Interrupts " ziemlich zwingende, synchrone Dinge, sodass sie keine Ad-hoc- Netzwerktopologien handhaben . Um dies zu beheben, wurden " Interrupts " in asynchrone Pakete mit hoher Priorität verallgemeinert, die als " Interrupt-Transaktionen " (im Kontext von USB) oder " nachrichtengesteuerte Interrupts " (im Kontext von PCI) bezeichnet werden. Dieses Protokoll ist in einer USB-Spezifikation beschrieben:

Bildbeschreibung hier eingeben

- " Abbildung 8-31. Bulk- / Steuer- / Interrupt-OUT-Transaktionshost-Zustandsmaschine " in "Universal Serial Bus Specification, Revision 2.0" , Printed-Page-222; PDF-Seite-250 (2000-04-27)

Das Wesentliche scheint zu sein, dass E / A-Geräte und Kommunikationskomponenten (wie USB-Hubs) im Grunde genommen wie Netzwerkgeräte funktionieren. Sie senden also Nachrichten, für die ihre Ports abgefragt werden müssen. Dies verringert den Bedarf an dedizierten Hardware-Leitungen.

Betriebssysteme wie Windows scheinen den Abfrageprozess selbst, zum Beispiel wie beschrieben in der Handhabung MSDN - Dokumentation für den USB_ENDPOINT_DESCRIPTOR‚s , die beschreibt , wie Sie steuern , wie oft Windows - Umfragen ein USB - Host - Controller für Interrupt / isochronen Meldungen:

Der bIntervalWert enthält das Abfrageintervall für Interrupt- und isochrone Endpunkte. Bei anderen Endpunkttypen sollte dieser Wert ignoriert werden. Dieser Wert gibt die Konfiguration des Geräts in der Firmware wieder. Treiber können es nicht ändern.

Das Abfrageintervall bestimmt zusammen mit der Geschwindigkeit des Geräts und dem Typ des Host-Controllers die Häufigkeit, mit der der Treiber einen Interrupt oder eine isochrone Übertragung initiieren soll. Der Wert in bIntervalstellt keine feste Zeitdauer dar. Dies ist ein relativer Wert, und die tatsächliche Abruffrequenz hängt auch davon ab, ob das Gerät und der USB-Host-Controller mit niedriger, voller oder hoher Geschwindigkeit arbeiten.

- "USB_ENDPOINT_DESCRIPTOR-Struktur" , Hardware Dev Center, Microsoft

Neuere Monitorverbindungsprotokolle wie DisplayPort scheinen dasselbe zu tun:

Multi-Stream-Transport (MST)

  • MST (Multi-Stream Transport) in DisplayPort Ver.1.2 hinzugefügt

    • In Version 1.1a war nur SST (Single-Stream-Transport) verfügbar
  • MST transportiert mehrere A / V-Streams über einen einzelnen Connector

    • Bis zu 63 Streams; nicht "Stream per Lane"

      • Unter diesen transportierten Strömen wird keine Synchronität angenommen. Ein Stream befindet sich möglicherweise in einer Austastperiode, während andere dies nicht tun
    • Ein verbindungsorientierter Transport

      • Pfad von einer Stream-Quelle zu einer Ziel-Stream-Senke, der über Message Transactions über AUX CHs vor dem Start einer Stream-Übertragung eingerichtet wurde

      • Hinzufügen / Löschen eines Streams ohne Auswirkungen auf die verbleibenden Streams

Bildbeschreibung hier eingeben

-Dia # 14 aus "DisplayPortTM Ver.1.2 Übersicht" (06.12.2010)

Diese Abstraktion ermöglicht einige nette Funktionen, wie das Ausführen von 3 Monitoren über eine Verbindung:

Mit DisplayPort Multi-Stream Transport können auch drei oder mehr Geräte miteinander verbunden werden, jedoch in der entgegengesetzten, weniger verbraucherorientierten Konfiguration: Mehrere Displays können gleichzeitig über einen einzigen Ausgangsport angesteuert werden.

- "DisplayPort" , Wikipedia

Aus konzeptioneller Sicht sollten Sie davon absehen, dass Abfragemechanismen eine allgemeinere serielle Kommunikation ermöglichen. Dies ist fantastisch, wenn Sie allgemeinere Funktionen wünschen. Die Hardware und das Betriebssystem führen also viele Abfragen für das logische System durch. Dann können Verbraucher, die Ereignisse abonnieren, diese Details genießen, die vom untergeordneten System für sie verarbeitet werden, ohne dass sie ihre eigenen Abruf- / Nachrichtenübermittlungsprotokolle schreiben müssen.

Letztendlich scheinen Ereignisse wie Tastendrücke eine ziemlich interessante Reihe von Ereignissen zu durchlaufen, bevor sie zum zwingenden Ereignisauslösemechanismus der Software-Ebene gelangen.

Nat
quelle
In Bezug auf Ihren letzten Absatz wird im Allgemeinen keine Abfrage auf niedriger Ebene durchgeführt. Das Betriebssystem reagiert auf Hardware-Interrupts, die von Peripheriegeräten ausgelöst werden. Ein Computer verfügt normalerweise über viele angeschlossene Geräte (Maus, Tastatur, Festplattenlaufwerke, Netzwerkkarten), und das Abrufen aller Geräte wäre sehr ineffizient.
Barmar
Ihre Analogien zur Postzustellung sind jedoch genau so, wie ich die Aktivität auf höherer Ebene erklären würde.
Barmar
1
@Barmar Als Geräte auf USB-Verbindungen umgestiegen sind, wurde viel darüber geredet, wie sie vom direkten Erzeugen von Interrupts (wie bei einer PS / 2-Tastatur) zum Abrufen (wie bei einer USB-Tastatur) übergegangen sind, und einige Quellen behaupten dass das Polling von der CPU durchgeführt wird. Aber andere Quellen behaupten , dass es auf einem speziellen Controller gemacht wird , die die Abfrage in eine Unterbrechung für die CPU umwandelt.
Nat
@Barmar Würdest du zufällig wissen, welches richtig ist? Ich habe wahrscheinlich gesehen, dass mehr Quellen behaupten, dass die CPU das Polling durchführt als sonst, aber ein spezialisierter Controller dafür scheint sinnvoller zu sein. Ich meine, ich denke, dass Arduino und andere eingebettete Geräte dazu neigen, die CPU zur Durchführung des Pollings zu zwingen, aber ich weiß nicht, ob es sich um x86-Geräte handelt.
Nat
1
Wenn jemand kann bestätigen , so kann ich diese Antwort aktualisieren, ich denke , dass moderne E / A - Geräte, zum Beispiel solche , die durch USB angeschlossen ist , direkt in den Speicher schreiben , der Kontrolle der CPU unter Umgehung (was beide , warum sie schnell / effizient und ein Sicherheits Gefahr zuweilen ). Dann ist ein modernes Betriebssystem erforderlich, um den Speicher auf neue Nachrichten abzufragen.
Nat
8

Pull vs Push

Es gibt zwei Hauptstrategien, um zu überprüfen, ob ein Ereignis eingetreten ist oder ein bestimmter Zustand erreicht ist. Stellen Sie sich zum Beispiel vor, Sie warten auf eine wichtige Lieferung:

  • Pull : gehe alle 10 Minuten zu deiner Mailbox und überprüfe, ob sie zugestellt wurde,
  • Push : Sagen Sie dem Zusteller, er soll Sie anrufen, wenn er die Lieferung erledigt.

Der Pull- Ansatz (auch Polling genannt) ist einfacher: Sie können ihn ohne spezielle Funktionen implementieren. Auf der anderen Seite ist es oft weniger effizient, da Sie riskieren, zusätzliche Überprüfungen durchzuführen, bei denen nichts angezeigt wird.

Auf der anderen Seite ist der Push- Ansatz im Allgemeinen effizienter: Ihr Code wird nur ausgeführt, wenn er etwas zu tun hat. Andererseits muss ein Mechanismus vorhanden sein, mit dem Sie einen Listener / Observer / Callback 1 registrieren können .

1 Leider fehlt meinem Postboten in der Regel ein solcher Mechanismus.

Matthieu M.
quelle
1

Über die Einheit im Einzelnen - es gibt keine andere Möglichkeit, die Eingaben des Spielers zu überprüfen, als sie bei jedem Frame abzufragen. Um einen Ereignis-Listener zu erstellen, benötigen Sie weiterhin ein Objekt wie "Ereignissystem" oder "Ereignismanager", um das Polling durchzuführen, sodass das Problem nur an eine andere Klasse weitergeleitet wird.

Zugegeben, sobald Sie einen Event-Manager haben, haben Sie nur eine Klasse, die die Eingaben pro Frame abfragt. Dies bietet jedoch keine offensichtlichen Leistungsvorteile, da diese Klasse nun über die Listener iterieren und diese aufrufen muss, was von Ihrem Spiel abhängt Design (wie in, wie viele Zuhörer gibt es und wie oft der Player Eingaben verwendet), könnte tatsächlich teurer sein.

Denken Sie außerdem an die goldene Regel: Vorzeitige Optimierung ist die Wurzel allen Übels. Dies gilt insbesondere für Videospiele, bei denen das Rendern jedes Frames häufig so viel kostet, dass kleine Skriptoptimierungen wie diese völlig bedeutungslos sind

Keine Ahnung
quelle
Ich würde eine zentrale Ereignisschleife nicht als Optimierung sehen, sondern als besser lesbaren, verständlichen Code, der sich gegen das Polling auf die gesamte Codebasis ausbreitet. Es erlaubt auch "synthetische" Ereignisse und Ereignisse, die nicht von der Abfrage der Game Engine stammen.
Blackjack
@BlackJack Ich stimme zu und ich codiere es normalerweise selbst so, aber das OP fragte nach der Leistung. Übrigens, Unity hat überraschend viele zweifelhafte Entscheidungen in Bezug auf das Code-Design getroffen, wie etwa statische Funktionen fast überall.
Keine Ahnung
1

Sofern Sie in Ihrem Betriebssystem / Framework keine Unterstützung für Ereignisse wie Tastendruck oder Timerüberlauf oder Nachrichtenankunft haben, müssen Sie dieses Ereignis-Listener-Muster trotzdem mithilfe von Abfragen implementieren (irgendwo darunter).

Aber wenden Sie sich nicht von diesem Entwurfsmuster ab, nur weil Sie dort nicht sofort einen Leistungsvorteil haben. Hier sind die Gründe, warum Sie es verwenden sollten, unabhängig davon, ob Sie Unterstützung für die Ereignisbehandlung haben oder nicht.

  1. Der Code sieht sauberer und isolierter aus (bei korrekter Implementierung natürlich)
  2. Der Code, der auf Ereignishandlern basiert, kann besser geändert werden (da normalerweise nur einige Ereignishandler geändert werden).
  3. Wenn Sie mit Unterstützung für zugrunde liegende Ereignisse auf die Plattform wechseln, können Sie Ihre vorhandenen Ereignishandler wiederverwenden und den Abrufcode einfach entfernen.

Fazit - Sie hatten das Glück, an der Diskussion teilzunehmen und eine Alternative zur Umfrage kennenzulernen. Wenn Sie nach einer Möglichkeit suchen, dieses Konzept in die Praxis umzusetzen, werden Sie feststellen, wie elegant der Code sein kann.

OpalApps
quelle
1

Die meisten Ereignisschleifen werden über einem vom Betriebssystem bereitgestellten Polling-Multiplexing-Grundelement erstellt. Unter Linux ist dieses Grundelement häufig der poll(2) Systemaufruf (kann aber auch der alte sein select). In GUI-Anwendungen kommuniziert der Anzeigeserver (z. B. Xorg oder Wayland ) über einen Socket (7) oder eine Pipe (7 ) mit Ihrer Anwendung. Lesen Sie auch über X Window System-Protokolle und -Architektur .

Solche Abfrageprimitive sind effizient; Der Kernel würde in der Praxis Ihren Prozess aufwecken, wenn eine Eingabe erfolgt (und eine Unterbrechung behandelt wird).

Konkret kommuniziert Ihre Widget-Toolkit- Bibliothek mit Ihrem Anzeigeserver, wartet auf Nachrichten und sendet diese Nachrichten an Ihre Widgets. Toolkit-Bibliotheken wie Qt oder GTK sind recht komplex (Millionen Zeilen Quellcode). Ihre Tastatur und Maus werden nur vom Prozess des Anzeigeservers verwaltet (der solche Eingaben in Ereignisnachrichten umsetzt, die an Clientanwendungen gesendet werden).

(Ich vereinfache; in der Tat sind die Dinge viel komplexer)

Basile Starynkevitch
quelle
1

In einem rein abrufbasierten System muss das Subsystem, das wissen möchte, wann eine bestimmte Aktion ausgeführt wird, zu jedem Zeitpunkt, zu dem diese Aktion ausgeführt wird, Code ausführen. Wenn es viele Subsysteme gibt, die innerhalb von 10 ms nach Auftreten eines nicht notwendigerweise eindeutigen Ereignisses reagieren müssten, müssten alle mindestens 100 Mal pro Sekunde prüfen, ob ihr Ereignis aufgetreten ist. Befinden sich diese Subsysteme in unterschiedlichen Prozessen in Threads (oder schlimmer noch in Prozessen), müssten diese Threads oder Prozesse jeweils 100x pro Sekunde umgeschaltet werden.

Wenn viele der Dinge, auf die Anwendungen achten werden, ziemlich ähnlich sind, kann es effizienter sein, ein zentrales Überwachungssubsystem - möglicherweise tabellengesteuert - zu haben, das viele Dinge beobachten und beobachten kann, ob sich eines von ihnen geändert hat. Wenn zum Beispiel 32 Switches vorhanden sind, kann eine Plattform die Funktion haben, alle 32 Switches gleichzeitig in ein Wort zu lesen. Auf diese Weise kann der Monitorcode überprüfen, ob sich Switches zwischen Abfragen geändert haben, und - falls nicht - nicht Sorgen Sie sich, welcher Code an ihnen interessiert sein könnte.

Wenn es viele Subsysteme gibt, die benachrichtigt werden sollen, wenn sich etwas ändert, ist es möglicherweise effizienter, wenn ein dediziertes Überwachungssubsystem andere Subsysteme benachrichtigt, wenn Ereignisse auftreten, an denen sie interessiert sind, als wenn jedes Subsystem seine eigenen Ereignisse abfragt. Die Einrichtung eines dedizierten Überwachungssubsystems in Fällen, in denen niemand an Ereignissen interessiert ist, würde jedoch eine reine Verschwendung von Ressourcen bedeuten. Wenn es nur wenige Subsysteme gibt, die an Ereignissen interessiert sind, können die Kosten für die Überwachung der Ereignisse, an denen sie interessiert sind, geringer sein als die Kosten für die Einrichtung eines allgemeinen dedizierten Überwachungs-Subsystems, aber die Rentabilität Punkt wird zwischen verschiedenen Plattformen erheblich variieren.

Superkatze
quelle
0

Ein Ereignis-Listener ist wie ein Ohr, das auf eine Nachricht wartet. Wenn das Ereignis eintritt, arbeitet das als Ereignis-Listener ausgewählte Unterprogramm mit den Ereignisargumenten.

Es gibt immer zwei wichtige Daten: den Zeitpunkt, an dem das Ereignis eintritt, und das Objekt, an dem dieses Ereignis eintritt. Ein weiteres Argument sind mehr Daten darüber, was passiert ist.

Der Ereignis-Listener gibt die Reaktion auf das Ereignis an.

Rafael Marazuela
quelle
0

Ein Event-Listener folgt dem Publish / Subscribe-Muster (als Abonnent)

In seiner einfachsten Form enthält ein Veröffentlichungsobjekt eine Liste der Anweisungen der Abonnenten, die auszuführen sind, wenn etwas veröffentlicht werden muss.

Es wird eine subscribe(x)Methode geben, bei der x davon abhängt, wie der Ereignishandler für die Behandlung des Ereignisses ausgelegt ist. Wenn subscribe (x) aufgerufen wird, wird x zur Verlegerliste der Anweisungen / Referenzen der Abonnenten hinzugefügt.

Der Herausgeber kann alle, einige oder keine der Logik zum Behandeln des Ereignisses enthalten. Möglicherweise sind lediglich Verweise auf die Abonnenten erforderlich, um sie mit der angegebenen Logik zu benachrichtigen / umzuwandeln, wenn das Ereignis eintritt. Es enthält möglicherweise keine Logik und erfordert Teilnehmerobjekte (Methoden / Ereignis-Listener), die das Ereignis verarbeiten können. Es ist am wahrscheinlichsten, eine Mischung aus beiden zu enthalten.

Wenn ein Ereignis eintritt, iteriert der Herausgeber und führt seine Logik für jedes Element in der Liste der Anweisungen / Referenzen der Abonnenten aus.

Egal wie komplex ein Event-Handler aussieht, im Kern folgt er diesem einfachen Muster.

Beispiele

Für ein Ereignis-Listener-Beispiel geben Sie der subscribe () -Methode des Ereignishandlers eine Methode / Funktion / Anweisung / Ereignis-Listener an. Der Ereignishandler fügt die Methode seiner Liste der Teilnehmerrückrufe hinzu. Wenn ein Ereignis eintritt, durchläuft der Ereignishandler seine Liste und führt jeden Rückruf aus.

Wenn Sie den Newsletter bei Stack Exchange abonnieren, wird ein Verweis auf Ihr Profil zu einer Datenbanktabelle mit Abonnenten hinzugefügt. Wenn der Newsletter veröffentlicht werden soll, wird anhand der Referenz eine Vorlage des Newsletters ausgefüllt und an Ihre E-Mail-Adresse gesendet. In diesem Fall ist x lediglich ein Verweis auf Sie, und der Herausgeber verfügt über eine Reihe interner Anweisungen, die für alle Abonnenten verwendet werden.

Dom
quelle