Werden Ereignisse nur für die GUI-Programmierung verwendet?

57

Werden Ereignisse nur für die GUI-Programmierung verwendet?

Wie gehen Sie bei der normalen Backend-Programmierung vor, wenn mit dieser anderen Sache etwas passiert?

user3093620
quelle
6
Übrigens ist Event Source ein völlig orthogonales Konzept zur Event-Programmierung. Das Grundkonzept von Event Sourcing besteht darin, dass Sie "Ereignisse" oder "Änderungen" in Ihrem System speichern, anstatt den "Status" Ihres Systems zu speichern. Sie können Ihr Bankkonto beispielsweise als a) Ihr Guthaben (STATE) oder b) eine Reihe von Transaktionen (EVENTSOURCE) modellieren.
Kunst
4
Abhängig von der Verwendung ist ein "Ereignis" normalerweise nur eine Form von Rückruf, der mit Zucker umhüllt ist. Rückrufe werden überall verwendet - wenn Sie interessiert sind, ist dies wahrscheinlich ein gutes Stichwort, mit dem Sie eine Suche starten können.
J ...
10
Ich werde auch darauf hinweisen, dass Hardware-Interrupts selbst dann eine nützliche und fragwürdige Funktion darstellen, wenn Sie so niedrig wie ein Mikrocontroller sind. Hervorragend geeignet für Steuerungssysteme oder grundlegende E / A-Funktionen wie eine Kaffeemaschine. Diese Hardware-Interrupts unterscheiden sich nicht wesentlich von Ereignissen.
Dan
Nee! Praktisches Beispiel: Nodejs Ereignisse
sampathsris
2
Bist du auf Windows? Überprüfen Sie die Ereignisanzeige. Habe Spaß.
Marc. 2377

Antworten:

106

Nee. Sie sind sehr praktisch, um Observer zu implementieren und sicherzustellen, dass Klassen für Änderungen gesperrt sind.

Angenommen, wir haben eine Methode, mit der neue Benutzer registriert werden.

public void Register(user) {
    db.Save(user);
}

Dann entscheidet jemand, dass eine E-Mail gesendet werden soll. Wir könnten dies tun:

public void Register(user) {
    db.Save(user);
    emailClient.Send(new RegistrationEmail(user));
}

Aber wir haben gerade eine Klasse geändert, die für Änderungen geschlossen sein soll. Wahrscheinlich in Ordnung für diesen einfachen Pseudocode, aber wahrscheinlich den Weg zum Wahnsinn im Produktionscode. Wie lange dauert es, bis diese Methode 30 Codezeilen enthält, die kaum mit dem ursprünglichen Zweck der Erstellung eines neuen Benutzers zusammenhängen?

Es ist viel angenehmer, die Klasse ihre Kernfunktionen ausführen zu lassen und ein Ereignis auszulösen, das jedem mitteilt, dass ein Benutzer registriert wurde, und er kann die erforderlichen Maßnahmen ergreifen (z. B. eine E-Mail senden).

public void Register(user) {
    db.Save(user);

    RaiseUserRegisteredEvent(user);
}

Dies hält unseren Code sauber und flexibel. Einer der häufig übersehenen Aspekte von OOP ist, dass Klassen sich gegenseitig Nachrichten senden . Ereignisse sind diese Nachrichten.

Badeente
quelle
37
Ich lese das und denke an unseren "Buchungscode erstellen" und weine eine Weile und sehne mich nach einem besseren Ort: '(
sara
1
+1, tolle Antwort. Nur eine tangentiale Frage (die mich ein bisschen gestört hat): Gibt es einen besonderen Grund, warum Sie Ihre Methodennamen (Registrieren, Speichern und Senden) mit Großbuchstaben begonnen haben? Dies hat natürlich keinen Einfluss auf die Nützlichkeit dieser Antwort.
Pedro A
14
@Hamsteriffic Ich bin hauptsächlich ein C # -Entwickler und das ist die allgemein akzeptierte Konvention. Kein anderer Grund.
RubberDuck
6
@Hamsterifficas ein Zusatz, was Sie lowerCase nennen, aber in der Mitte einen Großbuchstaben enthalten, wird oft camelCase genannt, weil es in der Mitte Buckel hat. Das unterscheidet es von snake_case, wo Wörter in Kleinbuchstaben durch Unterstriche getrennt sind, was Python und den meisten Shell-Sprachen vertraut ist, um nur einige zu nennen.
Aaron
5
Ereignisse sind eine Art dieser Nachrichten, würde ich sagen. Methodenaufrufe gelten ebenfalls als Message-Passing.
jpmc26
53

Nee.

Ein klassisches Beispiel für Ereignisse, die in einer Nicht-GUI-Logik verwendet werden, sind Datenbank-Trigger.

Trigger sind Code, der ausgeführt wird, wenn ein bestimmtes Ereignis eintritt (INSERT, DELETE usw.). Scheint für mich ein Ereignis zu sein.

Dies ist die Wikipedia-Definition des Ereignisses:

Beim Rechnen ist ein Ereignis eine von der Software erkannte Aktion oder ein Ereignis, das von der Software verarbeitet werden kann. Computerereignisse können vom System, vom Benutzer oder auf andere Weise generiert oder ausgelöst werden. In der Regel werden Ereignisse synchron mit dem Programmablauf verarbeitet. Das heißt, die Software verfügt möglicherweise über einen oder mehrere dedizierte Bereiche, an denen Ereignisse verarbeitet werden, häufig eine Ereignisschleife. Eine Ereignisquelle umfasst den Benutzer, der mit der Software beispielsweise über Tastenanschläge auf der Tastatur interagieren kann. Eine andere Quelle ist ein Hardwaregerät wie ein Timer. Die Software kann auch einen eigenen Ereignissatz in der Ereignisschleife auslösen, um z. B. den Abschluss einer Aufgabe mitzuteilen. Software, die ihr Verhalten als Reaktion auf Ereignisse ändert, wird als ereignisgesteuert bezeichnet, häufig mit dem Ziel, interaktiv zu sein.

Nicht alle Ereignisse werden vom Benutzer generiert. Einige werden von einem Timer wie einem crontab von einer Datenbank INSERT wie ich bereits erwähnt habe generiert.

Die Definition besagt auch, dass einige Programme oder Systeme "ereignisgesteuert sind, häufig mit dem Ziel, interaktiv zu sein" , woraus abgeleitet werden kann, dass der Zweck oder der Nutzen von Ereignissen nicht nur darin besteht, sondern häufig Interaktivität bereitzustellen (wie z. B. GUIs) obwohl nicht notwendigerweise GUIs, da CLI-Programme auch interaktiv sein können).

Tulains Córdova
quelle
2
Ich denke immer daran, wenn ich von Datenbank-Triggern höre: thecodelesscode.com/case/42
Almo
Als ehemaliger DBA-Entwickler erschaudere ich jedes Mal, wenn Leute über die Verwendung von Triggern sprechen, ohne die umfassendere DB-Leistung zu berücksichtigen.
Three Value Logic
27

Die ereignisbasierte Programmierung wird tatsächlich auch für die hochleistungsfähige Serverprogrammierung verwendet.

Bei einer typischen Serverauslastung stammt ein Großteil der Zeit für die Verarbeitung eines Ergebnisses aus E / A. Das Abrufen von Daten von einem Festplattenlaufwerk mit 7200 U / min kann beispielsweise bis zu 8,3 ms dauern. Bei einem modernen GHz-Prozessor entspräche dies ~ 1 Million Taktzyklen. Wenn eine CPU jedes Mal auf die Daten warten würde (nichts tun würde), würden wir VIELE Taktzyklen verlieren.

Herkömmliche Programmiertechniken umgehen dies, indem sie mehrere Threads einführen . Die CPU versucht, Hunderte von Threads gleichzeitig auszuführen. Das Problem ist jedoch, die in diesem Modell das heißt, jedes Mal , wenn ein CPU - Thread umschaltet, erfordert es Hunderte von Taktzyklen , um Kontextschalter . Ein Kontextwechsel erfolgt, wenn die CPU den Thread-lokalen Speicher in die Register der CPU kopiert und auch das Register / den Zustand des alten Threads im RAM speichert.

Zusätzlich muss jeder Thread eine bestimmte Menge an Speicher zum Speichern seines Status belegen.

Heute gab es einen Push für Server mit einem einzigen Thread, der in einer Schleife ausgeführt wird. Anschließend werden Arbeitsstücke auf eine Nachrichtenpumpe verschoben , die als Warteschlange für den einzelnen Thread fungiert (ähnlich wie bei einem UI-Thread). Anstatt auf die Beendigung der Arbeit zu warten, setzt die CPU ein Rückrufereignis für Dinge wie den Festplattenzugriff. Das reduziert die Kontextumschaltung.

Das beste Beispiel für einen solchen Server ist Node.js , von dem gezeigt wurde, dass es 1 Million gleichzeitige Verbindungen mit bescheidener Hardware verarbeiten kann, während ein Java / Tomcat- Server mit einigen Tausend kämpfen würde.

Kunst
quelle
2
"Bescheidene Hardware" ist etwas irreführend. Sie benötigen zusätzlich zu den Betriebssystemen 8 GB + für Node. Und wenn Sie so viel Speicher haben, kann Tomcat problemlos einige tausend Verbindungen verwalten. Zugegeben, es gibt einen großen Unterschied, aber es ist nicht 1000x.
Paul Draper
@PaulDraper nein kann es nicht. Und nein, tut es nicht. Sie benötigen 8 GB + nur für den Stapel für 8000 Threads. Das ist der große Unterschied.
Kunst
3
@PaulDraper ist neben 8GB für Serverstandards sehr bescheiden. Ich habe an Computern mit 128 GB RAM gearbeitet und diese sind noch nicht einmal voll geladen. Die RAM-Sticks kosten mehr als Ihre gesamte Maschine.
Kunst
Es kommt darauf an, wie groß Ihr Stapel ist. Oracle / OpenJDK verwendet auf den meisten Plattformen standardmäßig 1 MB für 64-Bit und 512 KB für 32-Bit. Wenn Sie nur die Standardeinstellungen übernehmen, sind Sie richtig. Aber die Standardeinstellungen sind nicht so, wie Sie zu 1M-Knotenverbindungen gelangen;) Wie auch immer, 128K sind ausreichend; Sie können mit noch weniger davonkommen. Das wäre 1 GB Stapelspeicher für 8000 Threads.
Paul Draper
6
Du liegst falsch. Selbst mit der Standardstapelgröße benötigen Sie nur 8 GB virtuellen Speicher. Der Speicher wird bei Bedarf im laufenden Betrieb festgeschrieben. Normalerweise müssen Sie nur eine einzige Seite pro Stapel, sind , wenn Sie tatsächlich mit den zusätzlichen Speicher. In der Praxis haben die meisten Threads in einem solchen System nur diese (normalerweise) 64-KB-Stapel. Die Nutzung des virtuellen Speichers ist unter 32-Bit-Betriebssystemen ein großes Problem, unter 64-Bit-Betriebssystemen jedoch weniger. Sie stoßen viel früher an andere Grenzen - wie zum Beispiel die Erschöpfung des TCP-Ports :) Und wo werden Ihrer Meinung nach diese "Pseudo-Stapel" von Knoten gespeichert? Das stimmt, auf dem Haufen.
Luaan
10

Ereignisse werden auch häufig in der Netzwerkprogrammierung verwendet (z. B. Nginx), um teure Warteschleifen zu vermeiden und stattdessen eine saubere Schnittstelle bereitzustellen, um genau zu wissen , wann eine bestimmte Operation verfügbar ist (E / A, dringende Daten usw.). Dies ist auch eine Lösung für das C10k-Problem .

Die Grundidee besteht darin, dem Betriebssystem eine Reihe von Sockets (dh Netzwerkverbindungen) zur Überwachung von Ereignissen bereitzustellen, die alle oder nur einige, an denen Sie besonders interessiert sind (z. B. zum Lesen verfügbare Daten). Wenn das Betriebssystem eine solche Aktivität auf einem der Sockets in der Liste erkennt, erhalten Sie eine Benachrichtigung über das Ereignis, das Sie von der API gesucht haben. Anschließend müssen Sie herausfinden, woher es kommt, und entsprechend vorgehen .

Nun, dies ist eine einfache und abstrakte Ansicht, die außerdem schwierig zu skalieren ist. Es gibt jedoch viele übergeordnete Frameworks, die sich plattformübergreifend damit befassen: Twisted für Python, Boost.Asio für C ++ oder libevent für C.

edmz
quelle
+1 "teure Busy-Wait-Schleifen": Mit anderen Worten, es ist nützlich in allen parallelen Prozessen, die eine gewisse Inaktivität (Warten) beinhalten, sowie bei der Synchronisierung zwischen solchen Prozessen (Nachrichten oder Ereignissen). So viel von der realen Welt funktioniert.
Freitag,
Es ist interessant herauszufinden, dass Sockets ursprünglich als eine Form von IPC auf einem einzelnen Computer entworfen wurden, bevor es ein Netzwerk gab.
5

Eingebettete Systeme sind fast immer inhärent ereignisgesteuert, auch wenn sie nicht explizit als solche programmiert sind.

Diese Ereignisse kommen von Dingen wie Hardware-Interrupts, Tastendrücken, periodischen Analog-Digital-Ablesungen, Timer-Ablaufen usw.

Embedded-Systeme mit geringem Stromverbrauch sind sogar eher ereignisgesteuert. Sie verbringen die meiste Zeit im Schlaf (CPU im Energiesparmodus) und warten darauf, dass etwas passiert (dieses "Etwas" ist ein Ereignis).

Eines der gebräuchlichsten und beliebtesten Frameworks für ereignisgesteuerte eingebettete Systeme ist die Quantum-Plattform (QP) (das QP funktioniert auch unter Linux, Windows und allen Unix-ähnlichen Betriebssystemen). da das Programm im typischen Sinne nicht "sequentiell" ist, handelt es sich vielmehr um eine Reihe von "Rückrufen", die abhängig vom Systemstatus und dem aktuellen Ereignis aufgerufen werden.

Radian
quelle
3

Ereignismeldungen Gregor Hohpe.

Ereignisgesteuerte Architekturen Gregor Hohpe.

SEDA-Architektur , Waliser, Culler, Brauer.

Wie gehen Sie bei der normalen Backend-Programmierung vor, wenn etwas passiert, machen Sie das andere?

Finite State Machine ist ein gängiger Ansatz

Given(State.A)
When(Event.B)
Then(State.C)
    .and(Consequences.D)
VoiceOfUnreason
quelle
2
1, dies ist keine wirklich schlüssige Antwort, und 2, FSM sind keine guten Beispiele für die Verwendung von Ereignissen.
Whatsisname
1
FSM. Ich bin sicher, dass die Pastafari-Religion eine Reihe von Ereignissen beobachtet
;-)
0

In eingebetteten Systemen treten Ereignisse während Interrupts auf. Es gibt viele Interruptquellen, von Timern bis zu E / A.

RTOS kann auch Ereignisse haben. Ein Beispiel wartet auf eine Nachricht von einer anderen Aufgabe.

Thomas Matthews
quelle
0

Für nicht eingebettete Systeme, aber etwas, was ich in C # tat, war SCADA-System. Es gab viele Ereignisse im Zusammenhang mit dem, was im Warehouse geschah, als das Laden eines Teils des vom System generierten Ereignisses beendet wurde und ein anderer Teil einen neuen Status in die Datenbank schrieb. Wir hatten natürlich einen GUI-Client, aber er sollte nur den Status der Datenbank anzeigen, der den Status des Lagers widerspiegelt. Es handelte sich also um Backend-Server-Software, die auf Ereignissen und Threading basierte. Sehr herausfordernd zu entwickeln.

https://en.wikipedia.org/wiki/SCADA

Mateusz
quelle