Postmessage verliert gelegentlich eine Nachricht
-
22-07-2019 - |
Frage
ich eine Multi-Threaded-Windows-Anwendung geschrieben, wo Gewinde:
A - ist eine Windows-Form, die Interaktion mit dem Benutzer verarbeitet und zu verarbeiten, um die Daten von B.
B - erzeugt gelegentlich Daten und übergibt es zwei A.
A threadsicher Warteschlange wird verwendet, um die Daten von Thread B zu A. Zum Bestehen der enqueue und dequeue Funktionen geschützt sind, um ein Windows-kritisches Abschnitt Objekte verwenden.
Wenn die Warteschlange leer ist, wenn die Enqueue-Funktion aufgerufen wird, wird die Funktion Postmessage verwendet A zu sagen, dass es Daten in der Warteschlange ist. Die Funktion überprüft der Anruf an Postmessage, um sicherzustellen, erfolgreich ausgeführt und wiederholt ruft Postmessage, wenn es nicht erfolgreich ist (Postmessage noch fehlschlagen hat).
Das funktionierte gut für einige Zeit, bis einer bestimmten Computer die gelegentliche Nachricht zu verlieren begann. Durch die lose ich meine, dass Postmessage erfolgreich in B gibt aber A erhält nie die Nachricht. Dies bewirkt, dass die Software eingefroren erscheinen.
Ich habe bereits mit ein paar annehmbaren Abhilfen kommen. Ich bin interessant zu wissen, warum Fenster werden diese Nachrichten verlieren und warum dies nur geschehen auf den einem Computer.
Hier sind die relevanten Teile des Codes.
// Only called by B
procedure TSharedQueue.Enqueue(AItem: TSQItem);
var
B: boolean;
begin
EnterCriticalSection(FQueueLock);
if FCount > 0 then
begin
FLast.FNext := AItem;
FLast := AItem;
end
else
begin
FFirst := AItem;
FLast := AItem;
end;
if (FCount = 0) or (FCount mod 10 = 0) then // just in case a message is lost
repeat
B := PostMessage(FConsumer, SQ_HAS_DATA, 0, 0);
if not B then
Sleep(1000); // this line of code has never been reached
until B;
Inc(FCount);
LeaveCriticalSection(FQueueLock);
end;
// Only called by A
function TSharedQueue.Dequeue: TSQItem;
begin
EnterCriticalSection(FQueueLock);
if FCount > 0 then
begin
Result := FFirst;
FFirst := FFirst.FNext;
Result.FNext := nil;
Dec(FCount);
end
else
Result := nil;
LeaveCriticalSection(FQueueLock);
end;
// procedure called when SQ_HAS_DATA is received
procedure TfrmMonitor.SQHasData(var AMessage: TMessage);
var
Item: TSQItem;
begin
while FMessageQueue.Count > 0 do
begin
Item := FMessageQueue.Dequeue;
// use the Item somehow
end;
end;
Lösung
Ist FCount
auch durch FQueueLock
geschützt? Wenn nicht, dann ist Ihr Problem liegt bei FCount
erhöht wird, nachdem die gesendete Nachricht verarbeitet wird, schon.
Hier ist, was passiert sein könnte:
- B tritt in kritischen Abschnitt
- B ruft
PostMessage
- A empfängt die Nachricht aber nicht alles tun, da
FCount
ist0
- B-Schritte
FCount
- B verlässt kritischen Abschnitt
- A sitzt da wie eine Ente
Eine schnelle Abhilfe wäre FCount
zu erhöhen, bevor PostMessage
aufrufen.
Beachten Sie, dass die Dinge schneller passieren können, als man erwarten würde (dh die Nachricht mit Postmessage geschrieben von einem anderen Thread gefangen und verarbeitet, bevor Sie eine Chance haben FCount ein paar Zeilen später zu erhöhen), vor allem, wenn Sie in einem sind echte Multi-threaded-Umgebung (mehr CPUs). Deshalb fragte ich früher, wenn die „Problem-Maschine“ hatte mehr CPUs / Cores.
Eine einfache Möglichkeit, Probleme wie diese zu beheben ist, den Code mit additonal Protokollierung Gerüst jedes Mal, geben Sie eine Methode, um sich einzuloggen, geben Sie / leave einen kritischen Abschnitt etc. Dann können Sie das Protokoll, um zu sehen, die wahre Reihenfolge der Ereignisse analysieren.
Auf einer separate Notiz, eine nette kleine Optimierung, die wie folgt in einem Producer / Consumer-Szenario durchgeführt werden kann, ist zwei Warteschlangen ein, anstatt zu verwenden. Wenn der Verbraucher die volle Warteschlange zu verarbeiten aufwacht, tauschen Sie die volle Warteschlange mit einer leeren und nur Verriegelungs- / Prozess die volle Warteschlange, während die neue leere Warteschlange kann, ohne dass die beiden Fäden bestückt werden versuchen, sich gegenseitig die Warteschlangen zu sperren. Sie würden immer noch, obwohl in der Vertauschung der beiden Warteschlangen eine Verriegelung benötigen.
Andere Tipps
Wenn die Warteschlange leer ist, wenn die Enqueue Funktion aufgerufen wird, wird die Funktion Verwenden von Postmessage A, dass es zu sagen, Daten in der Warteschlange ist.
Sperren Sie die Nachrichtenwarteschlange, bevor Sie die Größe der Warteschlange und Ausgeben der PostMessage
Überprüfung? Sie können eine Race-Bedingung erleben, wo Sie die Warteschlange überprüfen und nicht leer finden, wenn in der Tat eine der allerletzten Nachricht verarbeitet und bezieht sich auf Leerlauf gehen.
Um zu sehen, wenn Sie in der Tat erlebt eine Race-Bedingung und kein Problem mit PostMessage
sind, die Sie mit Hilfe eines Ereignisse wechseln könnten. Der Arbeiter-Thread (A) würde auf das Ereignis warten stattdessen auf eine Nachricht zu warten. B würde einfach das Ereignis gesetzt anstatt eine Nachricht zu veröffentlichen.
Das funktionierte gut für einige Zeit bis eines bestimmten Computer gestartet verliert die gelegentliche Meldung.
Durch eine Chance, nicht die Anzahl der CPUs oder Kerne, die diese spezielle Computer anders haben als die anderen, wo sehen Sie kein Problem? Manchmal, wenn man von einer Single-CPU-Maschine in eine Maschine wechselt mit mehr als einer physischen CPU / Kern, neue Rennbedingungen oder Deadlocks auftreten können.
Könnte es sein, eine zweite Instanz unwissentlich ausgeführt wird und die Nachrichten zu essen, markiert sie als behandelt?