Ist eine Ereignisschleife nur eine for / while-Schleife mit optimiertem Polling?

55

Ich versuche zu verstehen, was eine Ereignisschleife ist. Oft ist die Erklärung, dass Sie in einer Ereignisschleife so lange etwas tun, bis Sie benachrichtigt werden, dass ein Ereignis aufgetreten ist. Sie behandeln dann das Ereignis und setzen fort, was Sie zuvor getan haben.

Um die obige Definition mit einem Beispiel abzubilden. Ich habe einen Server, der in einer Ereignisschleife 'lauscht', und wenn eine Socket-Verbindung erkannt wird, werden die Daten von diesem Server gelesen und angezeigt, wonach der Server wie zuvor mit dem Abhören fortfährt / beginnt.


Es ist jedoch zu viel für mich, wenn dieses Ereignis passiert und wir einfach so benachrichtigt werden. Sie können sagen: "Es ist nicht einfach so, dass Sie einen Ereignis-Listener registrieren müssen." Was aber ein Ereignis-Listener ist, ist eine Funktion, die aus irgendeinem Grund nicht zurückkehrt. Befindet es sich in einer eigenen Schleife und wartet darauf, benachrichtigt zu werden, wenn ein Ereignis eintritt? Sollte der Ereignis-Listener auch einen Ereignis-Listener registrieren? Wo hört es auf?


Ereignisse sind eine nette Abstraktion, mit der man arbeiten kann, jedoch nur eine Abstraktion. Ich bin der Meinung, dass Abstimmungen am Ende unvermeidlich sind. Vielleicht machen wir das nicht in unserem Code, aber die niedrigeren Ebenen (die Implementierung der Programmiersprache oder das Betriebssystem) machen das für uns.

Es kommt im Grunde genommen auf den folgenden Pseudocode an, der irgendwo niedrig genug ist, so dass es nicht zu beschäftigtem Warten kommt:

while(True):
    do stuff
    check if event has happened (poll)
    do other stuff

Dies ist mein Verständnis der ganzen Idee und ich würde gerne hören, ob dies richtig ist. Ich bin offen dafür zu akzeptieren, dass die ganze Idee von Grund auf falsch ist. In diesem Fall möchte ich die richtige Erklärung.

TheMeaningfulEngineer
quelle
3
Ereignissysteme sind Implementierungen des Observer-Musters . Das Verständnis des Musters sollte Ihr Verständnis der Ereignisse festigen. Es ist kein Abruf erforderlich.
Steven Evers
1
Letztendlich ja, auch wenn die Sprache sie abstrahiert.
GroßmeisterB
@SteveEvers In deinem Wiki Link. Was EventSourcetun, wenn die Tastatureingabe nicht abgefragt wird?
TheMeaningfulEngineer
@Alan: Es kann alles Mögliche tun, aber für Tastatureingaben gibt es spezielle APIs, mit denen Sie Ihre Anwendung so registrieren können, dass sie Tastaturereignisse abhört, die Sie dann als Ereignisquelle verwenden können, ohne dass Abfragen erforderlich sind. Wenn Sie weit genug runter gehen, fragt USB natürlich immer ab , aber nehmen wir an, wir verwenden eine PS / 2-Tastatur, die durch Interrupts gesteuert wird, und dann haben Sie einen Tastatureingabestapel, der auf Ereignissen ohne Abfrage basiert.
Phoshi
Ich habe diese Fragen seit Jahren, aber ich kann nicht sagen, warum ich mich nie die Mühe gemacht habe, sie zu stellen. Danke, ich bin jetzt zufrieden mit dem Verständnis, mit dem mich Karl Bielefeldt aufgeklärt hat.
0xc0de

Antworten:

54

Die meisten Ereignisschleifen werden angehalten, wenn keine Ereignisse bereitstehen. Dies bedeutet, dass das Betriebssystem dem Task keine Ausführungszeit gibt, bis ein Ereignis eintritt.

Angenommen, das Ereignis ist eine gedrückte Taste. Sie werden möglicherweise gefragt, ob im Betriebssystem eine Schleife vorhanden ist, die nach Tastendrücken sucht. Die Antwort ist nein. Das Drücken von Tasten erzeugt einen Interrupt , der von der Hardware asynchron behandelt wird. Ebenso für Timer, Mausbewegungen, ein ankommendes Paket usw.

Tatsächlich ist das Abfragen von Ereignissen für die meisten Betriebssysteme die Abstraktion. Die Hardware und das Betriebssystem verarbeiten Ereignisse asynchron und stellen sie in eine Warteschlange, die von Anwendungen abgefragt werden kann. In eingebetteten Systemen sieht man nur echtes Polling auf Hardware-Ebene, und auch dort nicht immer.

Karl Bielefeldt
quelle
4
Ist eine Unterbrechung nicht eine Spannungsänderung an einem Draht? Kann das selbst ein Ereignis auslösen oder müssen wir den Pin nach dem Spannungswert abfragen?
TheMeaningfulEngineer
8
Das kann ein Ereignis von selbst auslösen. Der Prozessor ist so aufgebaut. Tatsächlich kann ein Prozessor durch einen Interrupt aus dem Ruhezustand geweckt werden.
Karl Bielefeldt
2
Ich bin mit der Aussage verwechselt Most event loops will block. Wie fügt sich dies in das "Paradigma der Ereignisschleife ein, das im Gegensatz zur Verwendung von Threads nicht blockierende asynchrone Aufrufe verwendet"?
TheMeaningfulEngineer
1
Wenn Sie sich Ereignisschleifen für eine Bibliothek wie GTK + ansehen, suchen diese nach neuen Ereignissen und rufen dann Ereignisbehandlungsroutinen in einer Schleife auf. Wenn jedoch keine Ereignisse vorhanden sind, blockieren sie ein Semaphor oder einen Timer oder etwas anderes. Einzelne Entwickler erstellen ihre eigenen Ereignisschleifen, die nicht in einer leeren Ereigniswarteschlange blockieren, sondern die weit verbreiteten Bibliotheken, die alle blockieren. Event-Schleifen sind sonst zu ineffizient.
Karl Bielefeldt
1
Ich nehme an, Sie könnten es so betrachten, aber es ist Multithreading in der Bibliothek und / oder im Betriebssystem und nicht im Anwendungscode. Ereignisschleifen abstrahieren die Synchronisationsprobleme für den Anwendungsentwickler.
Karl Bielefeldt
13

Ich stelle mir einen Event-Listener nicht als eine Funktion vor, die eine eigene Schleife ausführt, sondern als ein Staffellauf, bei dem der erste Läufer auf die Startpistole wartet. Ein wichtiger Grund für die Verwendung von Ereignissen anstelle von Abfragen besteht darin, dass sie bei CPU-Zyklen effizienter sind. Warum? Schau es dir von der Hardware aus an (und nicht vom Quellcode aus).

Betrachten Sie einen Webserver. Wenn Ihr Server aufruft listen()und blockiert, tritt Ihr Code als Relay-Runner an seine Stelle. Wenn das erste Paket einer neuen Verbindung eintrifft, startet die Netzwerkkarte das Rennen, indem sie das Betriebssystem unterbricht. Das Betriebssystem führt eine Interrupt-Service-Routine (ISR) aus, die das Paket erfasst. Der ISR übergibt den Staffelstab an eine übergeordnete Routine, die die Verbindung herstellt. Sobald die Verbindung hergestellt ist, gibt diese Routine den Taktstock an weiter listen(), der den Taktstock an Ihren Code weitergibt. An diesem Punkt können Sie mit der Verbindung tun, was Sie wollen. Nach allem, was wir wissen, könnte jeder Staffelläufer zwischen den Rennen in die Kneipe gehen. Eine Stärke der Ereignisabstraktion ist, dass Ihr Code nichts wissen oder sich darum kümmern muss.

Einige Betriebssysteme enthalten Ereignishandhabungscode, der seinen Teil des Rennens ausführt, den Taktstock loslässt und dann zu seinem Startpunkt zurückkehrt, um auf den Start des nächsten Rennens zu warten. In diesem Sinne wird die Ereignisbehandlung für das Abrufen in vielen gleichzeitigen Schleifen optimiert. Es gibt jedoch immer einen externen Trigger, der den Prozess auslöst. Der Ereignis-Listener ist keine Funktion, die nicht zurückgegeben wird, sondern eine Funktion, die auf diesen externen Trigger wartet, bevor er ausgeführt wird. Eher, als:

while(True):
    do stuff
    check if event has happened (poll)
    do other stuff

Ich betrachte dies als:

on(some event):    //I got the baton
     do stuff
     signal the next level up    //Pass the baton

und zwischen dem signalund dem nächsten Mal, wenn der Handler ausgeführt wird, wird konzeptionell kein Code ausgeführt oder eine Schleife ausgeführt.

cxw
quelle
Dies ist jedoch sinnvoll. Was macht die Ereignisschleife, während sie auf den Interrupt wartet? Wenn es blockiert, ist das nicht der "Multi-Threaded" -Ansatz, der das Paradigma ist, das der Ereignisschleife entgegengesetzt ist?
TheMeaningfulEngineer
Wenn Ihr Code die Schleife enthält forever: { select(); do stuff; }, treten Sie jedes Mal über die Schleife wieder in das Rennen ein. Egal, ob Sie dies wiederholt aus einem einzelnen Thread oder parallel auf separaten Threads oder Prozessoren tun, ich stelle mir jedes Ereignis als eine eigene Rasse vor. Ein Webbrowser ist beispielsweise ein Multithread-Programm mit mehreren Ereignisschleifen in separaten Threads, mindestens eine für die Benutzeroberfläche und eine für jede heruntergeladene Seite. Die Frage, die ich beim Codieren stelle, lautet: "Wie kann ich Ereignisse schnell genug verarbeiten?" Manchmal ist die Antwort eine Schleife, manchmal Fäden, oft eine Kombination.
cxw
Ich programmiere seit mehr als ein paar Jahrzehnten ereignisbasiert und fand diese Antwort sehr verwirrend. Damit ein Listener funktioniert, muss eine Schleife vorhanden sein, die auf ein Ereignis wartet und dieses dann an alle registrierten Listener weiterleitet. (Angenommen, es geht eher um Software als um Hardware-Interrupts.)
Bryan Oakley,
8

Nein, es handelt sich nicht um "optimiertes Polling". Eine Ereignisschleife verwendet eine Interrupt-gesteuerte E / A, anstatt eine Abfrage durchzuführen.

While-, Until-, For- usw.-Schleifen sind Polling-Schleifen.

"Polling" ist der Vorgang, bei dem wiederholt etwas überprüft wird. Da die Codeschleife führt kontinuierlich, und weil es eine kleine, „dicht“ Schleife, gibt es wenig Zeit für den Prozessor Aufgaben zu wechseln und etwas anderes tun. Fast alle "Hänge", "Einfrieren", "Abstürze" oder wie auch immer Sie es nennen möchten, wenn der Computer nicht mehr reagiert, sind die Manifestation von Code, der in einer unbeabsichtigten Abfrageschleife steckt. Die Instrumentierung zeigt eine 100% ige CPU-Auslastung an.

Interruptgesteuerte Ereignisschleifen sind weitaus effizienter als Abfrageschleifen. Polling ist eine äußerst verschwenderische Nutzung von CPU-Zyklen, daher werden alle Anstrengungen unternommen, um diese zu eliminieren oder zu minimieren.

Um die Codequalität zu optimieren, versuchen die meisten Sprachen jedoch, das Polling-Schleifen-Paradigma für Event-Handing-Befehle so genau wie möglich zu verwenden, da sie in einem Programm funktional ähnlichen Zwecken dienen. Da das Abrufen die bekanntere Methode ist, auf einen Tastendruck oder etwas anderes zu warten, ist es für den Unerfahrenen einfach, es zu verwenden und ein Programm zu starten, das möglicherweise von selbst einwandfrei läuft, während es ausgeführt wird, funktioniert jedoch nichts anderes. Es hat die Maschine "übernommen".

Wie in anderen Antworten erläutert, wird beim Interrupt-gesteuerten Event-Handing im Wesentlichen ein "Flag" in der CPU gesetzt und der Prozess wird "angehalten" (darf nicht ausgeführt werden), bis dieses Flag von einem anderen Prozess (wie der Tastatur) geändert wird Fahrer, der es ändert, wenn der Benutzer eine Taste gedrückt hat). Wenn das Flag ein tatsächlicher Hardware-Zustand ist, wie beispielsweise eine Leitung, die "hochgezogen" ist, wird es "Interrupt" oder "Hardware-Interrupt" genannt. Die meisten sind jedoch nur als Speicheradresse in der CPU oder im Hauptspeicher (RAM) implementiert und werden als "Semaphore" bezeichnet.

Semaphore können softwaregesteuert geändert werden und bieten so einen sehr schnellen und einfachen Signalisierungsmechanismus zwischen Softwareprozessen.

Interrupts können jedoch nur durch Hardware geändert werden. Am häufigsten werden Interrupts verwendet, die in regelmäßigen Abständen vom internen Clock-Chip ausgelöst werden. Eine der unzähligen Arten von Softwareaktionen, die durch Taktunterbrechungen aktiviert werden, ist das Ändern von Semaphoren.

Ich habe viel ausgelassen, musste aber irgendwo aufhören. Bitte fragen Sie, ob Sie weitere Informationen benötigen.

DocSalvager
quelle
7

In der Regel ist die Antwort Hardware, das Betriebssystem und die Hintergrundthreads, die Sie nicht kontrollieren, verschwören sich, damit es mühelos aussieht. Die Netzwerkkarte empfängt einige Daten und löst einen Interrupt aus , um die CPU zu informieren. Der Interrupt-Handler des Betriebssystems erledigt dies. Dann wird ein Hintergrund-Thread, den Sie nicht kontrollieren (der durch die Registrierung für das Ereignis erstellt wurde und der seit Ihrer Registrierung für das Ereignis nicht mehr aktiv ist), vom Betriebssystem als Teil der Ereignisbehandlung aufgeweckt und führt Ihren Ereignishandler aus.

Steinmetalle
quelle
7

Ich gehe gegen alle anderen Antworten, die ich bisher sehe, und sage "Ja" . Ich denke, die anderen Antworten erschweren die Dinge zu sehr. Aus konzeptioneller Sicht sind alle Ereignisschleifen im Wesentlichen:

while <the_program_is_running> {
    event=wait_for_next_event()
    process_event(event)
}

Wenn Sie zum ersten Mal versuchen, Ereignisschleifen zu verstehen, schadet es nicht, wenn Sie sie als einfache Schleife betrachten. Einige zugrunde liegende Frameworks warten auf die Übermittlung eines Ereignisses durch das Betriebssystem, leiten das Ereignis dann an einen oder mehrere Handler weiter und warten dann auf das nächste Ereignis usw. Aus Sicht der Anwendungssoftware ist das wirklich alles.

Bryan Oakley
quelle
1
+1 für unkompliziert. Der Schwerpunkt liegt jedoch auf dem "Warten". Die Ausführung dieses Codes stoppt in der ersten Zeile der Schleife und wird erst fortgesetzt, wenn ein Ereignis eintritt. Dies unterscheidet sich von einer ausgelasteten Abrufschleife, die wiederholt nach einem Ereignis sucht, bis eines eintritt.
Tom Panning
1

Nicht alle Ereignisauslöser werden in Schleifen behandelt. So schreibe ich oft meine eigenen Event-Engines:

interface Listener {
    void handle (EventInfo info);
}

List<Listener> registeredListeners

void triggerEvent (EventInfo info) {
    foreach (listener in registeredListeners) { // Memo 1
        listener.handle(info) // the handling may or may not be synchronous... your choice
    }
}

void somethingThatTriggersAnEvent () {
    blah
    blah
    blah
    triggerEvent(someGeneratedEventInfo)
    more blah
}

Beachten Sie, dass sich Memo 1 zwar in einer Schleife befindet, die Schleife jedoch zur Benachrichtigung der einzelnen Listener dient. Der Ereignisauslöser selbst befindet sich nicht unbedingt in einer Schleife.

Theoretisch können Schlüsselereignisse auf Betriebssystemebene dieselbe Technik verwenden (obwohl ich denke, dass sie häufig stattdessen abfragen? Ich spekuliere hier nur), sofern das Betriebssystem eine Art registerListenerAPI bereitstellt.

Thomas Eding
quelle