Was sind Alternativen zur Win32 PulseEvent () Funktion?

8

Die Dokumentation für die Win32-API PulseEvent() -Funktion ( kernel32.dll ) gibt an, dass diese Funktion "... unzuverlässig ist und nicht von neuen Anwendungen verwendet werden sollte. Verwenden Sie stattdessen Bedingungsvariablen ". Bedingungsvariablen können jedoch nicht über Prozessgrenzen hinweg verwendet werden, wie (benannte) Ereignisse können.

Ich habe ein Szenario, das prozessübergreifend und laufzeitübergreifend ist (nativer und verwalteter Code), in dem ein einzelner Produzent gelegentlich etwas interessantes hat, um null oder mehr Konsumenten bekannt zu machen. Im Moment wird ein bekanntes benanntes Ereignis vom Hersteller verwendet (und auf den Status "Signalisiert" gesetzt), der diese PulseEvent-Funktion verwendet, wenn es etwas bekannt machen muss. Null oder mehr Verbraucher warten auf dieses Ereignis ( WaitForSingleObject()) und führen eine Aktion als Antwort durch. In meinem Szenario ist keine wechselseitige Kommunikation erforderlich und der Produzent muss nicht wissen, ob das Ereignis Listener hat oder nicht Ich muss wissen, ob die Veranstaltung erfolgreich durchgeführt wurde, andererseits möchte ich, dass keine Verbraucher irgendwelche Ereignisse verpassen, dh das System muss absolut zuverlässig sein - aber der Produzent muss nicht wissen, ob das der Fall ist oder nicht.Das Szenario kann man sich als "Uhr-Ticker" vorstellen - dh der Produzent liefert ein halb-reguläres Signal für null oder mehr Konsumenten, um zu zählen. Und alle Konsumenten muss über einen bestimmten Zeitraum hinweg die korrekte Anzahl haben.Es ist keine Abfrage durch Konsumenten erlaubt (Leistungsgründe) .Der Ticker ist nur ein paar Millisekunden (20 oder so, aber nicht perfekt regulär).

Rayman Chen (Die alte neue Sache) hat einen Blogbeitrag aus der "grundlegend fehlerhaft" Natur der PulseEvent () - Funktion, aber ich sehe keine Alternative für mein Szenario von Chen oder die geposteten Kommentare.

Kann jemand bitte einen vorschlagen?

Beachten Sie, dass das IPC-Signal Prozessgrenzen auf der Maschine überschreiten muss, nicht nur Threads. Und die Lösung muss eine hohe Leistung aufweisen, da die Verbraucher in der Lage sein müssen, innerhalb von 10 ms nach jedem Ereignis zu handeln.

    
Bill 27.04.2010, 22:57
quelle

5 Antworten

6

Ich denke, Sie werden etwas Komplexeres brauchen, um Ihr Zuverlässigkeitsziel zu erreichen.

Mein Verständnis Ihres Problems besteht darin, dass Sie einen Produzenten und eine unbekannte Anzahl von Konsumenten haben, die alle verschiedene Prozesse sind. Jeder Verbraucher kann keine Ereignisse verpassen.

Ich hätte gerne mehr Klarheit darüber, was ein Ereignis vermisst.

i) Wenn ein Verbraucher gestartet und kurz vor dem Warten auf Ihre Benachrichtigungsmethode gestartet wurde und ein Ereignis aufgetreten ist, sollte es verarbeitet werden, obwohl es zu dem Zeitpunkt, an dem die Benachrichtigung gesendet wurde, noch nicht fertig war? (d. h. wann wird ein Verbraucher als aktiv betrachtet? wenn er startet oder wenn er sein erstes Ereignis verarbeitet)

ii) Wenn der Verbraucher ein Ereignis verarbeitet und der Code, der auf die nächste Benachrichtigung wartet, noch nicht mit dem Warten begonnen hat (ich nehme eine Wait -> Process -> Loop to Wait -Struktur an), sollte er wissen, dass ein anderes Ereignis eingetreten ist während es herumschlenderte?

Ich würde annehmen, dass i) ein "nicht wirklich" ist, da es ein Rennen zwischen Prozessbeginn und "fertig sein" ist und ii) "ja" ist; Das heißt, Benachrichtigungen werden effektiv in die Warteschlange für jeden Kunden gestellt, sobald der Verbraucher anwesend ist und jeder Verbraucher alle Ereignisse, die während seiner aktiven Zeit produziert werden, konsumiert und nicht überspringt.

Sie möchten also einen Stream von Benachrichtigungen an eine Gruppe von Konsumenten senden, bei denen ein Verbraucher garantiert auf alle Benachrichtigungen in diesem Stream reagiert, von dem Punkt, an dem er auf den ersten bis zu dem Punkt wirkt, an dem er agiert es schließt sich. d. h. wenn der Produzent den folgenden Benachrichtigungsstrom erzeugt

1 2 3 4 5 6 7 8 9 0

und Verbraucher a) startet und verarbeitet 3, sollte auch 4-0

verarbeiten

wenn Verbraucher b) startet und 5 verarbeitet, aber nach 9 heruntergefahren wird, dann sollte es 5,6,7,8,9

verarbeitet haben

Wenn c) beim Start der Benachrichtigungen ausgeführt wurde, sollte es 1-0 verarbeitet haben

usw.

Das Pulsen eines Ereignisses funktioniert nicht. Wenn ein Verbraucher nicht aktiv auf das Ereignis wartet, wenn das Ereignis gepulst wird, wird das Ereignis nicht erfasst. Daher werden wir versagen, wenn Ereignisse schneller produziert werden, als wir umkehren können, um erneut auf das Ereignis zu warten.

Die Verwendung eines Semaphors funktioniert auch nicht so, als ob ein Verbraucher schneller als ein anderer Verbraucher in einem solchen Ausmaß läuft, dass er zu dem Semaphor-Aufruf umlaufen kann, bevor der andere die Verarbeitung abschließt, und wenn es innerhalb dieser Zeit eine weitere Benachrichtigung gibt Ereignis mehr als einmal und man könnte eins verpassen. Das heißt, Sie können 3 Threads freigeben (wenn der Hersteller weiß, dass es 3 Konsumenten gibt), aber Sie können nicht sicherstellen, dass jeder Verbraucher nur einmal veröffentlicht wird.

Ein Ringpuffer von Ereignissen (Tick-Zählimpulsen) im gemeinsamen Speicher, wobei jeder Verbraucher den Wert des zuletzt verarbeiteten Ereignisses und mit über ein gepulstes Ereignis alarmierten Verbrauchern kennt, sollte auf Kosten einiger der nicht synchronisierten Verbraucher arbeiten die Zecken manchmal; Wenn sie eines verpassen, werden sie das nächste Mal aufholen, wenn sie gepulst werden. Solange der Ringpuffer groß genug ist, damit alle Verbraucher die Ereignisse verarbeiten können, bevor der Erzeuger den Puffer durchläuft, sollten Sie in Ordnung sein.

Wenn im obigen Beispiel der Verbraucher d den Impuls für Ereignis 4 verpasst, weil er zu diesem Zeitpunkt nicht auf sein Ereignis gewartet hat und sich dann in eine Wartezeit begibt, wird er beim Erzeugen von Ereignis 5 und seit seiner letzten Verarbeitung geweckt gezählt ist 3, es wird 4 und 5 verarbeitet und dann zurück zum Ereignis ...

Wenn das nicht gut genug ist, würde ich etwas vorschlagen wie PGM über Sockets, um Ihnen ein zuverlässiges Multicast zu geben; Der Vorteil wäre, dass Sie Ihre Kunden auf verschiedene Maschinen bringen könnten ...

    
Len Holgate 28.04.2010, 08:24
quelle
2

Es gibt zwei inhärente Probleme mit PulseEvent :

  • Wenn es bei Ereignissen mit automatischem Zurücksetzen verwendet wird, gibt es nur einen Kellner frei.
  • -Threads werden möglicherweise nie aktiviert, wenn sie aufgrund von APC zum Zeitpunkt des PulseEvent .
  • aus der Warteschlange gelöscht werden

Eine Alternative besteht darin, eine Fensternachricht zu senden, und jeder Listener hat eine Nachricht auf oberster Ebene - nur ein Fenster, das diese bestimmte Nachricht abhört.

Der Hauptvorteil dieses Ansatzes besteht darin, dass Sie Ihren Thread nicht explizit blockieren müssen. Der Nachteil dieses Ansatzes besteht darin, dass Ihre Listener STA sein müssen (in einem MTA-Thread kann keine Nachrichtenwarteschlange vorhanden sein).

Das größte Problem bei diesem Ansatz wäre, dass die Verarbeitung des Ereignisses durch den Listener um die Zeit verzögert wird, die die Warteschlange benötigt, um zu dieser Nachricht zu gelangen.

Sie können auch sicherstellen, dass Sie manuelle Reset-Ereignisse verwenden (damit alle wartenden Threads aufgeweckt werden) und SetEvent / ResetEvent mit einer kleinen Verzögerung (sagen wir 150ms) ausführen, um Threats eine größere Chance zu geben APC, um Ihre Veranstaltung abzuholen.

Natürlich hängt es davon ab, wie oft Sie Ihre Ereignisse auslösen müssen und ob die Listener jedes Ereignis oder nur das letzte Ereignis verarbeiten sollen, wenn einer dieser alternativen Ansätze für Sie funktioniert.

    
Franci Penov 27.04.2010 23:16
quelle
2

Der Grund, warum PulseEvent "unzuverlässig" ist, liegt nicht so sehr an etwas in der Funktion selbst, sondern daran, dass Ihr Kunde nicht gerade auf den Event im exact moment dass PulseEvent aufgerufen wird, wird es vermissen.

In Ihrem Szenario ist die beste Lösung, den Zähler manuell zu behalten. Der Producer-Thread zählt also den aktuellen "Tick-Tick", und wenn ein Consumer-Thread gestartet wird, liest er den aktuellen Wert dieses Counters. Dann, anstatt PulseEvent zu verwenden, erhöhen Sie den Zähler "Clock Ticks" und verwenden Sie SetEvent , um alle Threads zu aktivieren, die auf das Häkchen warten. Wenn der Consumer-Thread aufwacht, prüft er seinen "Tick-Tick" -Wert gegen die "Tick-Ticks" des Produzenten und weiß, wie viele Ticks verstrichen sind. Kurz bevor es erneut auf das Ereignis wartet, kann es überprüfen, ob ein anderes Häkchen aufgetreten ist.

Ich bin mir nicht sicher, ob ich das oben sehr gut beschrieben habe, aber hoffentlich gibt das eine Idee:)

    
Dean Harding 27.04.2010 23:17
quelle
0

Wenn ich Ihre Frage richtig verstanden habe, scheint es, als könnten Sie einfach SetEvent verwenden. Es wird einen Thread freigeben. Stellen Sie nur sicher, dass es sich um ein Auto-Reset-Ereignis handelt.

Wenn Sie mehrere Threads zulassen möchten, können Sie einen benannten Semaphor mit CreateSemaphore verwenden. Jeder Aufruf von ReleaseSemaphore erhöht die Anzahl. Wenn die Zählung beispielsweise 3 ist und 3 Threads darauf warten, werden alle ausgeführt.

    
Mark Wilkins 27.04.2010 23:09
quelle
0

Ereignisse eignen sich besser für die Kommunikation zwischen den Laufflächen innerhalb eines Prozesses (unbenannte Ereignisse). Wie Sie beschrieben haben, haben Sie null oder mehr Kunden, die etwas Interessiertes lesen müssen. Ich verstehe, dass sich die Anzahl der Clients dynamisch ändert. In diesem Fall ist die beste Wahl eine benannte Pipe.

Named Pipe ist König

Wenn Sie Daten nur an mehrere Prozesse senden müssen, ist es besser, Named Pipes zu verwenden, nicht die Ereignisse. Im Gegensatz zu Ereignissen mit automatischer Zurücksetzung benötigen Sie für jeden der Clientprozesse keine eigene Pipe. Jede benannte Pipe hat einen zugeordneten Serverprozess und einen oder mehrere zugeordnete Clientprozesse (und sogar null). Wenn viele Clients vorhanden sind, werden vom jeweiligen Betriebssystem für jeden der Clients automatisch viele Instanzen derselben Named Pipe erstellt. Alle Instanzen einer benannten Pipe teilen denselben Pipe-Namen, aber jede Instanz hat ihre eigenen Puffer und Handles und bietet einen separaten Conduit für die Client / Server-Kommunikation. Die Verwendung von Instanzen ermöglicht mehreren Pipe-Clients die gleichzeitige Verwendung derselben Named Pipe. Jeder Prozess kann sowohl als Server für eine Pipe als auch als Client für eine andere Pipe fungieren und umgekehrt, wodurch eine Peer-to-Peer-Kommunikation möglich wird.

Wenn Sie eine Named Pipe verwenden, sind die Ereignisse in Ihrem Szenario überhaupt nicht erforderlich, und die Daten werden garantiert geliefert, unabhängig davon, was mit den Prozessen geschieht - jeder der Prozesse kann lange Verzögerungen (z. B. durch einen Tausch), aber die Daten werden schließlich ohne Ihre besondere Beteiligung so schnell wie möglich geliefert.

Bei den Ereignissen

Wenn Sie noch an den Events interessiert sind - das Auto-Reset-Event ist König! ☺

Die CreateEvent-Funktion hat das Argument bManualReset. Wenn dieser Parameter TRUE ist, erstellt die Funktion ein Event-Objekt für das manuelle Zurücksetzen, das die Verwendung der ResetEvent-Funktion erfordert, um den Ereigniszustand auf nicht signalisiert zu setzen. Das ist nicht was du brauchst. Wenn dieser Parameter FALSE ist, erstellt die Funktion ein Ereignisobjekt mit automatischem Zurücksetzen, und das System setzt den Ereignisstatus automatisch auf "Nicht signalisiert" zurück, nachdem ein einzelner wartender Thread freigegeben wurde.

Diese Auto-Reset-Ereignisse sind sehr zuverlässig und einfach zu bedienen.

Wenn Sie mit WaitForMultipleObjects oder WaitForSingleObject auf ein Ereignisobjekt mit automatischem Zurücksetzen warten, setzt es das Ereignis beim Beenden dieser Wartefunktionen zuverlässig zurück.

So erstellen Sie Ereignisse auf folgende Weise:

%Vor%

Warten Sie auf das Ereignis von einem Thread und führen Sie SetEvent von einem anderen Thread aus. Dies ist sehr einfach und sehr zuverlässig.

Rufen Sie niemals ResetEvent (da es automatisch zurückgesetzt wird) oder PulseEvent auf (da es nicht zuverlässig und veraltet ist). Selbst Microsoft hat zugegeben, dass PulseEvent nicht verwendet werden sollte. Siehe Ссылка

Diese Funktion ist unzuverlässig und sollte nicht verwendet werden, da nur solche Threads benachrichtigt werden, die sich zum Zeitpunkt des Aufrufs von PulseEvent im Wartezustand befinden. Wenn sie sich in einem anderen Status befinden, werden sie nicht benachrichtigt, und Sie werden nie sicher wissen, wie der Thread-Status ist. Ein Thread, der auf ein Synchronisationsobjekt wartet, kann momentan durch einen asynchronen Prozedur-Aufruf im Kernelmodus aus dem Wartezustand entfernt werden und kehrt dann in den Wartezustand zurück, nachdem die APC abgeschlossen ist. Wenn der Aufruf von PulseEvent während der Zeit auftritt, in der der Thread aus dem Wartezustand entfernt wurde, wird der Thread nicht freigegeben, da PulseEvent nur die Threads freigibt, die im Moment des Aufrufs warten.

Sie können mehr über asynchrone Prozeduraufrufe im Kernelmodus unter den folgenden Links erfahren:

Wir haben PulseEvent noch nie in unseren Anwendungen eingesetzt. Wie bei Auto-Reset-Ereignissen verwenden wir sie seit Windows NT 3.51 (obwohl sie in der ersten 32-Bit-Version von NT-3.1 erschienen sind) und sie funktionieren sehr gut.

Ihr Interprozess-Szenario

Leider ist Ihr Fall ein bisschen komplizierter. Sie haben mehrere Threads in mehreren Prozessen, die auf ein Ereignis warten, und Sie müssen sicherstellen, dass alle Threads tatsächlich die Benachrichtigung erhalten haben. Es gibt keinen anderen zuverlässigen Weg, als für jeden Kunden ein eigenes Ereignis zu erstellen. Sie müssen also so viele Ereignisse haben wie die Konsumenten. Darüber hinaus müssen Sie eine Liste der registrierten Verbraucher führen, wobei jedem Verbraucher ein Ereignisname zugeordnet ist. Um also alle Kunden zu benachrichtigen, müssen Sie SetEvent in einer Schleife für alle Verbraucherereignisse durchführen. Dies ist ein sehr schneller, zuverlässiger und billiger Weg.Da Sie die prozessübergreifende Kommunikation verwenden, müssen die Benutzer ihre Ereignisse über andere Methoden der Kommunikation zwischen Prozessen, wie SendMessage, registrieren und abmelden. Wenn sich beispielsweise ein Consumer-Prozess bei Ihrem Haupt-Notifier-Prozess registriert, sendet er SendMessage an Ihren Prozess, um einen eindeutigen Ereignisnamen anzufordern. Sie erhöhen nur den Zähler und geben etwas wie Ereignis1, Ereignis2 usw. zurück und erstellen Ereignisse mit diesem Namen, sodass die Benutzer vorhandene Ereignisse öffnen. Wenn der Benutzer die Registrierung auflöst, schließt er die Ereignishandle, die er zuvor geöffnet hat, und sendet eine weitere SendMessage, um Sie darüber zu informieren, dass Sie CloseHandle ebenfalls auf Ihrer Seite haben sollten, um dieses Ereignisobjekt endgültig freizugeben. Wenn der Konsumentenprozess abstürzt, werden Sie mit einem Dummy-Ereignis enden, da Sie nicht wissen, dass Sie CloseHandle tun sollten, aber das sollte kein Problem sein - die Ereignisse sind sehr schnell und sehr billig, und es gibt praktisch keine Begrenzung die Kernel-Objekte - die Pro-Prozess-Grenze für Kernel-Handles ist 2 ^ 24. Wenn Sie immer noch besorgt sind, können Sie das Gegenteil tun - die Clients erstellen die Ereignisse, aber Sie öffnen sie. Wenn sie sich nicht öffnen, ist der Client abgestürzt und Sie entfernen ihn einfach aus der Liste.

    
Maxim Masiutin 10.05.2017 20:56
quelle

Tags und Links