Ereignisschleife - Event loop

In der Informatik ist die Ereignisschleife ein Programmierkonstrukt oder ein Entwurfsmuster, das auf Ereignisse oder Nachrichten in einem Programm wartet und diese versendet . Die Ereignisschleife funktioniert, indem sie eine Anfrage an einen internen oder externen "Ereignisanbieter" stellt (der die Anforderung im Allgemeinen blockiert, bis ein Ereignis eingetroffen ist) und dann den entsprechenden Ereignishandler aufruft ("versendet das Ereignis"). Die Ereignisschleife wird manchmal auch als Message Dispatcher , Message Loop , Message Pump oder Run Loop bezeichnet .

Die Ereignis-Schleife kann mit einem in Verbindung verwendet wird Reaktor , wenn der Ereignisanbieter folgt die Dateischnittstelle , die ausgewählt werden kann , oder ‚abgefragt‘ (der UNIX - Systemaufruf, nicht unbedingt den Polling ). Die Ereignisschleife arbeitet fast immer asynchron mit dem Nachrichtenabsender.

Wenn die Ereignisschleife das zentrale Kontrollflusskonstrukt eines Programms bildet, wie es häufig der Fall ist, kann sie als Hauptschleife oder Hauptereignisschleife bezeichnet werden . Dieser Titel ist angemessen, da eine solche Ereignisschleife die höchste Kontrollebene innerhalb des Programms ist.

Nachricht übergeben

Nachrichtenpumpen werden gesagt, ‚Pumpe‘ Nachrichten aus dem Programm - Nachrichtenwarteschlange (zugeordnet und in der Regel durch das zugrundeliegende Betriebssystem gehört) in das Programm für die Verarbeitung. Im engeren Sinne ist eine Ereignisschleife eine der Methoden zur Umsetzung der Interprozesskommunikation . Tatsächlich existiert die Nachrichtenverarbeitung in vielen Systemen, einschließlich einer Kernel-Level- Komponente des Mach-Betriebssystems . Die Ereignisschleife ist eine spezielle Implementierungstechnik von Systemen, die Nachrichtenweitergabe verwenden .

Alternative Designs

Dieser Ansatz steht im Gegensatz zu einer Reihe anderer Alternativen:

  • Traditionell wurde ein Programm einfach einmal ausgeführt und dann beendet. Diese Art von Programm war in den frühen Tagen des Computers weit verbreitet und es fehlte jegliche Form von Benutzerinteraktivität. Dies wird immer noch häufig verwendet, insbesondere in Form von kommandozeilengesteuerten Programmen. Alle Parameter werden vorab eingerichtet und beim Programmstart in einem Rutsch übergeben.
  • Menügesteuerte Designs. Diese können immer noch eine Hauptschleife aufweisen, werden aber normalerweise nicht als ereignisgesteuert im üblichen Sinne angesehen. Stattdessen wird dem Benutzer ein immer enger werdender Satz von Optionen präsentiert, bis die Aufgabe, die er ausführen möchte, die einzige verfügbare Option ist. Eingeschränkte Interaktivität durch die Menüs ist verfügbar.

Verwendungszweck

Aufgrund der Dominanz grafischer Benutzeroberflächen verfügen die meisten modernen Anwendungen über eine Hauptschleife. Die get_next_message()Routine wird typischerweise vom Betriebssystem bereitgestellt und blockiert, bis eine Nachricht verfügbar ist. Somit wird die Schleife nur betreten, wenn etwas zu verarbeiten ist.

function main
    initialize()
    while message != quit
        message := get_next_message()
        process_message(message)
    end while
end function

Dateischnittstelle

Unter Unix führt das Paradigma " Alles ist eine Datei " natürlich zu einer dateibasierten Ereignisschleife. Das Lesen aus und das Schreiben in Dateien, die Kommunikation zwischen Prozessen, die Netzwerkkommunikation und die Gerätesteuerung werden alle mithilfe von Datei-E/A erreicht, wobei das Ziel durch einen Dateideskriptor identifiziert wird . Die Systemaufrufe select und poll ermöglichen die Überwachung eines Satzes von Dateideskriptoren auf eine Zustandsänderung, zB wenn Daten zum Lesen verfügbar werden.

Stellen Sie sich zum Beispiel ein Programm vor, das aus einer ständig aktualisierten Datei liest und deren Inhalt im X Window System anzeigt , das mit Clients über einen Socket (entweder Unix-Domäne oder Berkeley ) kommuniziert :

def main():
    file_fd = open("logfile.log")
    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()

Umgang mit Signalen

Eines der wenigen Dinge in Unix, die nicht der Dateischnittstelle entsprechen, sind asynchrone Ereignisse ( Signale ). Signale werden in Signalhandlern empfangen , kleinen, begrenzten Codestücken, die ausgeführt werden, während der Rest der Aufgabe ausgesetzt wird; wenn ein Signal empfangen und verarbeitet wird, während die Aufgabe blockiert select()ist, kehrt select vorzeitig mit EINTR zurück ; Wenn ein Signal empfangen wird, während die Task CPU-gebunden ist , wird die Task zwischen den Anweisungen ausgesetzt, bis der Signalhandler zurückkehrt.

Ein offensichtlicher Weg, Signale zu handhaben, besteht für Signalhandler darin, ein globales Flag zu setzen und die Ereignisschleife für das Flag unmittelbar vor und nach dem select()Aufruf überprüfen zu lassen ; Wenn es gesetzt ist, behandeln Sie das Signal auf die gleiche Weise wie bei Ereignissen auf Dateideskriptoren. Leider führt dies zu einer Race-Condition : Wenn ein Signal unmittelbar zwischen dem Überprüfen des Flags und dem Aufrufen eintrifft select(), wird es select()aus einem anderen Grund (z.

Die Lösung von POSIX ist der pselect()Aufruf, der ähnlich ist, select()aber einen zusätzlichen sigmaskParameter benötigt, der eine Signalmaske beschreibt . Dies ermöglicht einer Anwendung, Signale in der Hauptaufgabe zu maskieren und dann die Maske für die Dauer des select()Aufrufs zu entfernen, sodass Signalhandler nur aufgerufen werden, während die Anwendung E/A-gebunden ist . Allerdings waren Implementierungen von pselect()nicht immer zuverlässig; Linux-Versionen vor 2.6.16 haben keinen pselect()Systemaufruf, wodurch es vermieden werden soll, glibc zu zwingen , es über eine Methode zu emulieren, die anfällig für die gleiche Race-Condition pselect()ist.

Eine alternative, portablere Lösung besteht darin, asynchrone Ereignisse mit dem Self-Pipe-Trick in dateibasierte Ereignisse umzuwandeln , bei dem "ein Signalhandler ein Byte in eine Pipe schreibt, deren anderes Ende select()im Hauptprogramm von überwacht wird ". In der Linux-Kernel- Version 2.6.22 wurde ein neuer Systemaufruf signalfd()hinzugefügt, der das Empfangen von Signalen über einen speziellen Dateideskriptor ermöglicht.

Implementierungen

Windows-Anwendungen

Auf der Microsoft Windows - Betriebssystem, ein Prozess, der in Wechselwirkung mit dem Benutzer muss akzeptieren und reagieren auf eingehende Nachrichten, die von einem getan fast unweigerlich Nachrichtenschleife in diesem Prozess. In Windows wird eine Nachricht mit einem Ereignis gleichgesetzt, das vom Betriebssystem erzeugt und auferlegt wird. Ein Ereignis kann unter anderem eine Benutzerinteraktion, Netzwerkverkehr, Systemverarbeitung, Zeitgeberaktivität, Kommunikation zwischen Prozessen sein. Für nicht interaktive, nur E/A-Ereignisse verfügt Windows über E/A-Abschlussports . E/A-Abschluss-Portschleifen werden getrennt von der Message-Schleife ausgeführt und interagieren nicht standardmäßig mit der Message-Schleife.

Das "Herzstück" der meisten Win32- Anwendungen ist die Funktion WinMain() , die GetMessage() in einer Schleife aufruft . GetMessage() blockiert, bis eine Nachricht oder ein "Ereignis" empfangen wird (mit der Funktion PeekMessage() als nicht blockierende Alternative). Nach einer optionalen Verarbeitung ruft es DispatchMessage() auf , das die Nachricht an den entsprechenden Handler sendet , auch bekannt als WindowProc . Normalerweise werden Nachrichten, die kein spezielles WindowProc() haben, an DefWindowProc gesendet , den Standardwert. DispatchMessage() ruft den WindowProc des HWND- Handle der Nachricht (mit der Funktion RegisterClass() registriert ) auf.

Nachrichtenbestellung

Neuere Versionen von Microsoft Windows garantieren dem Programmierer, dass Nachrichten an die Nachrichtenschleife einer Anwendung in der Reihenfolge übermittelt werden, in der sie vom System und seinen Peripheriegeräten wahrgenommen wurden. Diese Garantie ist unerlässlich, wenn man die Konsequenzen für das Design von Multithread- Anwendungen berücksichtigt .

Für einige Nachrichten gelten jedoch andere Regeln, z. B. Nachrichten, die immer zuletzt empfangen werden, oder Nachrichten mit einer anderen dokumentierten Priorität.

X Window-System

Xlib-Ereignisschleife

X- Anwendungen, die Xlib direkt verwenden, basieren auf der XNextEventFunktionsfamilie; XNextEventblockiert, bis ein Ereignis in der Ereigniswarteschlange erscheint, woraufhin die Anwendung es entsprechend verarbeitet. Die Xlib-Ereignisschleife verarbeitet nur Fenstersystemereignisse; Anwendungen, die in der Lage sein müssen, auf andere Dateien und Geräte zu warten, könnten ihre eigene Ereignisschleife aus primitiven Elementen wie erstellen ConnectionNumber, verwenden jedoch in der Praxis Multithreading .

Nur sehr wenige Programme verwenden Xlib direkt. Im häufigeren Fall unterstützen GUI-Toolkits, die auf Xlib basieren, normalerweise das Hinzufügen von Ereignissen. Auf Xt Intrinsics basierende Toolkits haben beispielsweise XtAppAddInput()und XtAppAddTimeout().

Bitte beachten Sie, dass es nicht sicher ist, Xlib-Funktionen von einem Signalhandler aus aufzurufen, da die X-Anwendung in einem beliebigen Zustand, zB innerhalb von unterbrochen worden sein kann XNextEvent. Siehe [1] für eine Lösung für X11R5, X11R6 und Xt.

GLib-Ereignisschleife

Die GLib- Ereignisschleife wurde ursprünglich für die Verwendung in GTK erstellt , wird aber jetzt auch in Nicht-GUI-Anwendungen wie D-Bus verwendet . Die abgefragte Ressource ist die Sammlung von Dateideskriptoren, an denen die Anwendung interessiert ist; der Polling-Block wird unterbrochen, wenn ein Signal ankommt oder ein Timeout abläuft (zB wenn die Anwendung einen Timeout oder Idle Task vorgegeben hat). Während GLib integrierte Unterstützung für Dateideskriptor- und untergeordnete Beendigungsereignisse bietet, ist es möglich, eine Ereignisquelle für jedes Ereignis hinzuzufügen, das in einem Vorbereitungs-Prüfungs-Versand-Modell behandelt werden kann. [2]

Zu den Anwendungsbibliotheken, die auf der GLib-Ereignisschleife aufgebaut sind, gehören GStreamer und die asynchronen I/O- Methoden von GnomeVFS , aber GTK bleibt die sichtbarste Clientbibliothek. Ereignisse aus dem Windowing-System (in X den X- Socket auslesen ) werden von GDK in GTK-Ereignisse übersetzt und als GLib-Signale an die Widget-Objekte der Anwendung ausgegeben.

macOS Core Foundation-Laufschleifen

Pro Thread ist genau ein CFRunLoop erlaubt und es können beliebig viele Quellen und Beobachter angehängt werden. Quellen kommunizieren dann mit Beobachtern über die Laufschleife, wobei diese die Warteschlange und den Versand von Nachrichten organisiert.

Der CFRunLoop wird in Cocoa als NSRunLoop abstrahiert, wodurch jede Nachricht (entspricht einem Funktionsaufruf in nicht reflektierenden Laufzeiten) zum Versand an ein beliebiges Objekt in die Warteschlange gestellt werden kann.

Siehe auch

Verweise

Externe Links