it-swarm.com.de

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

Ich versuche zu verstehen, was eine Ereignisschleife ist. Oft ist die Erklärung, dass Sie in einer Ereignisschleife etwas tun, bis Sie benachrichtigt werden, dass ein Ereignis aufgetreten ist. Anschließend bearbeiten Sie das Ereignis und machen weiter, was Sie zuvor getan haben.

Abbildung der obigen Definition anhand eines Beispiels. Ich habe einen Server, der in einer Ereignisschleife "lauscht", und wenn eine Socket-Verbindung erkannt wird, werden die Daten von ihr gelesen und angezeigt. Danach nimmt der Server das Abhören wieder auf/beginnt wie zuvor.


Dieses Ereignis passiert jedoch und wir werden "einfach so" benachrichtigt. Das ist zu viel für mich. Sie können sagen: "Es ist nicht einfach so, dass Sie einen Ereignis-Listener registrieren müssen." Aber was ist ein Ereignis-Listener, aber 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 endet es?


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

Grundsätzlich kommt es auf den folgenden Pseudocode an, der irgendwo niedrig genug ist, damit nicht viel gewartet wird:

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

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

57

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

Angenommen, das Ereignis ist eine Taste, die gedrückt wird. Möglicherweise fragen Sie, ob sich irgendwo im Betriebssystem eine Schleife befindet, die nach Tastendrücken sucht. Die Antwort ist nein. Durch Drücken von Tasten wird ein Interrupt generiert, der von der Hardware asynchron behandelt wird. Ebenso für Timer, Mausbewegungen, ein ankommendes Paket usw.

Tatsächlich ist für die meisten Betriebssysteme das Abrufen von Ereignissen die Abstraktion. Die Hardware und das Betriebssystem behandeln Ereignisse asynchron und stellen sie in eine Warteschlange, die von Anwendungen abgefragt werden kann. In eingebetteten Systemen sieht man wirklich nur echte Abfragen auf Hardwareebene, und selbst dort nicht immer.

56
Karl Bielefeldt

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 Startwaffe wartet. Ein wesentlicher Grund für die Verwendung von Ereignissen anstelle von Abfragen ist, dass sie mit CPU-Zyklen effizienter sind. Warum? Schauen Sie es sich von der Hardware an (und nicht vom Quellcode an).

Betrachten Sie einen Webserver. Wenn Ihr Server listen() aufruft und blockiert, tritt Ihr Code an seine Stelle als Relay Runner. 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, übergibt diese Routine den Staffelstab an listen(), wodurch der Staffelstab an Ihren Code übergeben wird. An diesem Punkt können Sie mit der Verbindung tun, was Sie wollen. Nach allem, was wir wissen, könnte zwischen den Rennen jeder Staffelläufer in die Kneipe gehen. Eine Stärke der Ereignisabstraktion ist, dass Ihr Code nichts wissen oder sich darum kümmern muss.

Einige Betriebssysteme enthalten Code für die Ereignisbehandlung, der seinen Teil des Rennens ausführt, den Staffelstab abgibt und dann zum Startpunkt zurückkehrt, um auf den Start des nächsten Rennens zu warten. In diesem Sinne ist die Ereignisbehandlung eine optimierte Abfrage in vielen gleichzeitigen Schleifen. Es gibt jedoch immer einen externen Auslöser, der den Prozess startet. Der Ereignis-Listener ist keine Funktion, die nicht zurückkehrt, 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 signal und dem nächsten Ausführen des Handlers wird konzeptionell kein Code ausgeführt oder wiederholt.

13
cxw

Nein, es ist kein "optimiertes Polling". Eine Ereignisschleife verwendet Interrupt-gesteuerte E/A anstelle von Abfragen.

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

"Polling" ist der Vorgang, bei dem wiederholt etwas überprüft wird. Da der Schleifencode kontinuierlich ausgeführt wird und weil eine kleine, "enge" Schleife ist, bleibt dem Prozessor wenig Zeit, um Aufgaben zu wechseln und etwas anderes zu 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 100% CPU-Auslastung.

Interrupt-gesteuerte Ereignisschleifen sind weitaus effizienter als Abrufschleifen. Polling ist eine äußerst verschwenderische Verwendung von CPU-Zyklen, daher werden alle Anstrengungen unternommen, um diese zu beseitigen oder zu minimieren.

Um die Codequalität zu optimieren, versuchen die meisten Sprachen jedoch, das Polling-Loop-Paradigma für Ereignisübergabebefehle so genau wie möglich zu verwenden, da sie innerhalb eines Programms funktional ähnlichen Zwecken dienen. Da das Abrufen die bekanntere Art ist, auf einen Tastendruck oder etwas anderes zu warten, ist es für Unerfahrene einfach, ihn zu verwenden und ein Programm zu erstellen, das möglicherweise von selbst einwandfrei ausgeführt wird, aber nichts anderes funktioniert, während es ausgeführt wird. Es hat die Maschine "übernommen".

Wie in anderen Antworten erläutert, wird bei der Interrupt-gesteuerten Ereignisübergabe im Wesentlichen ein "Flag" innerhalb der CPU gesetzt und der Prozess wird "angehalten" (darf nicht ausgeführt werden), bis dieses Flag durch einen anderen Prozess (wie die Tastatur) geändert wird Treiber ändert es, wenn der Benutzer eine Taste gedrückt hat). Wenn das Flag eine tatsächliche Hardwarebedingung ist, z. B. eine Leitung, die "hochgezogen" wird, wird es als "Interrupt" oder "Hardware-Interrupt" bezeichnet. Die meisten werden jedoch nur als Speicheradresse auf der CPU oder im Hauptspeicher (RAM) implementiert und als "Semaphoren" bezeichnet.

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

Interrupts können jedoch nur durch Hardware geändert werden. Die am weitesten verbreitete Verwendung von Interrupts ist die, die in regelmäßigen Abständen vom internen Taktchip ausgelöst wird. Eine der unzähligen Arten von Softwareaktionen, die durch Taktinterrupts aktiviert werden, ist das Ändern von Semaphoren.

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

8
DocSalvager

In der Regel lautet die Antwort Hardware, das Betriebssystem und die Hintergrundthreads, die Sie nicht steuern, verschwören sich, damit es mühelos aussieht. Die Netzwerkkarte empfängt einige Daten, die ein Interrupt auslösen, um die CPU zu informieren. Der Interrupt-Handler des Betriebssystems übernimmt dies. Dann wird ein Hintergrundthread, den Sie nicht steuern (der durch die Registrierung für das Ereignis erstellt wurde und seit Ihrer Registrierung für das Ereignis in den Ruhezustand versetzt wurde), vom Betriebssystem im Rahmen der Behandlung des Ereignisses aktiviert und führt Ihren Ereignishandler aus.

7
stonemetal

Ich werde gegen alle anderen Antworten, die ich bisher sehe, vorgehen und "ja" sagen. 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, sie als einfache Schleife zu betrachten. Einige zugrunde liegende Frameworks warten darauf, dass das Betriebssystem ein Ereignis übermittelt, leiten das Ereignis dann an einen oder mehrere Handler weiter, warten dann auf das nächste Ereignis und so weiter. Das ist wirklich alles, was es aus Sicht der Anwendungssoftware gibt.

7
Bryan Oakley

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 jedes Listeners 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 stattdessen häufig Abfragen durchführen? Ich spekuliere hier nur), vorausgesetzt, das Betriebssystem stellt eine Art registerListener API bereit.

1
Thomas Eding