Stellen Sie sicher, dass alle TThread.Queue-Methoden abgeschlossen sind, bevor der Thread selbst zerstört wird

8

Ich habe festgestellt, dass, wenn eine mit TThread.Queue in die Warteschlange eingereihte Methode eine Methode aufruft, die TApplication.WndProc aufruft (z. B. ShowMessage ), nachfolgende in der Warteschlange stehende Methoden dürfen ausgeführt werden, bevor die ursprüngliche Methode abgeschlossen wurde. Schlimmer noch, sie scheinen nicht in FIFO-Reihenfolge aufgerufen zu werden.

[Bearbeiten: Tatsächlich beginnen sie in FIFO-Reihenfolge. Mit ShowMessage sieht es so aus, als würde der spätere zuerst ausgeführt, da ein Aufruf von CheckSynchronize vor dem Erscheinen des Dialogs erfolgt. Dies hebt die nächste Methode auf und führt sie aus, wobei sie erst zurückkehrt, wenn die letztere Methode abgeschlossen ist. Erst dann erscheint der Dialog.]

Ich versuche sicherzustellen, dass alle Methoden, die aus dem Worker-Thread in den VCL-Thread eingereiht wurden, in strikter FIFO-Reihenfolge ausgeführt werden und dass sie alle abgeschlossen sind, bevor der Worker-Thread zerstört wird.

Meine andere Einschränkung besteht darin, dass ich versuche, die GUI strikt von der Geschäftslogik zu trennen. Der Thread ist in diesem Fall Teil des Geschäftslogik-Layers, daher kann ich PostMessage von einem OnTerminate -Handler nicht verwenden, um dafür zu sorgen, dass der Thread zerstört wird (wie von einer Reihe von Mitwirkenden an anderer Stelle empfohlen). Also setze ich FreeOnTerminate := True in einer letzten eingereihten Methode, kurz bevor TThread.Execute beendet wird. (Daher müssen sie in einer strengen FIFO-Reihenfolge ausgeführt werden.)

So endet meine TThread.Execute-Methode:

%Vor%

Aber wie gesagt, das funktioniert nicht, weil diese in die Warteschlange gestellte Methode vor einigen der früheren Methoden in der Warteschlange ausgeführt wird.

Kann jemand einen einfachen und sauberen Weg vorschlagen, um das zu schaffen, oder eine einfache und saubere Alternative?

[Die einzige Idee, die ich bis jetzt entwickelt habe und die Trennung von Bedenken und Modularität beibehält, besteht darin, meiner TThread -Unterklasse ein WndProc zu geben. Dann kann ich PostMessage direkt zu diesem WndProc anstelle des Hauptformulars verwenden. Aber ich hoffe auf etwas Leichtes.]

Danke für die Antworten und Kommentare bis jetzt. Ich verstehe jetzt, dass mein Code oben mit einem eingereihten SetEvent und WaitForSingleObject funktional dem Aufruf von Synchronize am Ende entspricht anstatt von Queue , weil Queue und Synchronize die gleiche Warteschlange teilen. Ich habe Synchronize zuerst ausprobiert und dies ist aus dem gleichen Grund fehlgeschlagen, wie der obige Code fehlschlägt - die früher eingereihten Methoden rufen die Nachrichtenbehandlung auf, sodass die letzte Synchronize -Methode ausgeführt wird, bevor die zuvor eingereihten Methoden abgeschlossen wurden.

Ich bin also immer noch an das ursprüngliche Problem gebunden, das nun auf Folgendes hinausläuft: Kann ich sauber sicherstellen, dass alle in der Warteschlange befindlichen Methoden abgeschlossen sind, bevor der Worker-Thread freigegeben wird, und den Worker-Thread sauber freigeben kann, ohne% zu verwenden? co_de%, für das ein Fenster-Handle benötigt wird, auf das gebucht werden soll (auf das meine Business-Schicht keinen Zugriff hat).

Ich habe den Titel auch besser aktualisiert, um das ursprüngliche Problem widerzuspiegeln, obwohl ich mich für eine alternative Lösung freuen würde, die PostMessage gegebenenfalls nicht verwendet. Wenn sich jemand einen besseren Titel ausdenken kann, dann bitte editieren.

Ein weiteres Update: Diese Antwort von David Heffernan empfiehlt die Verwendung von TThread.Queue mit einem speziellen PostMessage im allgemeinen Fall wenn AllocateHWnd nicht verfügbar oder geeignet ist. Bezeichnenderweise ist es nie sicher, TThread.Queue für das Hauptformular zu verwenden, da das Fenster spontan neu erstellt werden kann, wenn sein Handle geändert wird, wodurch alle nachfolgenden Nachrichten an das alte Handle verloren gehen würden. Dies ist ein starkes Argument für mich, diese spezielle Lösung zu übernehmen, da es keinen zusätzlichen Aufwand für das Erstellen eines versteckten Fensters in meinem Fall gibt, da any -Anwendung das PostMessage verwenden soll - also mein Argument "Trennung von Interessen" irrelevant.

    
Ian Goldby 29.04.2014, 08:34
quelle

4 Antworten

1

Dies ist die Lösung, die ich schließlich angenommen habe.

Ich habe Delphi TCountdownEvent verwendet, um die Anzahl der ausstehenden Methoden in der Warteschlange zu verfolgen aus meinem Thread, inkrementieren die Zählung kurz vor dem Einreihen einer Methode, und dekrementieren es als der letzte Akt der in die Warteschlange eingereihten Methode.

Kurz bevor mein Überschreiben von TThread.Execute zurückkehrt, wartet es darauf, dass das TCountdownEvent -Objekt signalisiert wird, d. h. wenn der Zählwert Null erreicht. Dies ist der entscheidende Schritt, der garantiert, dass alle in die Warteschlange gestellten Methoden abgeschlossen sind, bevor Execute zurückgibt.

Sobald alle in der Warteschlange stehenden Methoden abgeschlossen sind, ruft sie Synchronize mit einem OnComplete -Handler auf - dank Remy, um darauf hinzuweisen, dass dies äquivalent zu, aber einfacher als mein ursprünglicher Code ist, der Queue und WaitForSingleObject verwendet . ( OnComplete ist wie OnTerminate , wird aber vor Execute aufgerufen, damit der Handler FreeOnTerminate ändern kann.)

Die einzige Falte ist, dass TCountdownEvent.AddCount nur funktioniert, wenn die Anzahl bereits größer als Null ist. Also habe ich einen Klassenhelfer geschrieben, um ForceAddCount zu implementieren:

%Vor%

Normalerweise wäre das riskant, aber in meinem Fall wissen wir, dass zu dem Zeitpunkt, an dem der Thread auf die Anzahl der ausstehenden Methoden wartet, um Null zu erreichen, keine weiteren Methoden mehr in die Warteschlange gestellt werden können bleib bei Null).

Dies löst nicht vollständig das Problem von eingereihten Methoden, die Nachrichten verarbeiten, da einzelne eingereihte Methoden immer noch nicht ordnungsgemäß funktionieren. Aber ich habe jetzt die Garantie, dass alle in der Warteschlange befindlichen Methoden asynchron ausgeführt werden, aber abgeschlossen sein werden, bevor der Thread beendet wird. Dies war das primäre Ziel, da es dem Thread ermöglichte, sich selbst aufzuräumen, ohne das Risiko zu haben, in die Warteschlange gestellte Methoden zu verlieren.

    
Ian Goldby 08.05.2014, 08:16
quelle
5

TThread.Queue() ist eine FIFO-Warteschlange. Tatsächlich teilt es dieselbe Warteschlange, die Thread.Sychronize() verwendet. Aber Sie haben Recht, dass die Nachrichtenbehandlung dazu führt, dass in der Warteschlange befindliche Methoden ausgeführt werden. Dies liegt daran, dass TApplication.Idle() Aufrufe von CheckSynchronize() immer dann aufruft, wenn die Nachrichtenwarteschlange nach der Verarbeitung neuer Nachrichten inaktiv wird. Wenn also eine eingereihte / synchronisierte Methode die Nachrichtenverarbeitung aufruft, können andere in der Warteschlange stehende / synchronisierte Methoden ausgeführt werden, selbst wenn die frühere Methode noch ausgeführt wird.

Wenn Sie sicherstellen möchten, dass eine Warteschlangenmethode aufgerufen wird, bevor der Thread beendet wird, sollten Sie Synchronize() anstelle von Queue() verwenden oder stattdessen das Ereignis OnTerminate verwenden (das von Synchronize() ausgelöst wird). Was Sie in Ihrem finally -Block tun, ist eigentlich das Gleiche wie das OnTerminate -Ereignis bereits nativ tut.

Das Festlegen von FreeOnTerminate := True in einer eingereihten -Methode verlangt nach einem Speicherverlust. FreeOnTerminate wird sofort ausgewertet, wenn Execute() beendet wird, bevor DoTerminate() aufgerufen wird, um das OnTerminate -Ereignis auszulösen (was meiner Meinung nach ein Versehen ist, wenn es so früh bewertet wird, dass OnTerminate nicht zum Zeitpunkt der Beendigung entscheidet, ob a Thread sollte sich selbst freigeben oder nicht, nachdem OnTerminate beendet wurde). Wenn also die Methode in der Warteschlange ausgeführt wird, nachdem Execute() beendet wurde, gibt es keine Garantie dafür, dass FreeOnTerminate rechtzeitig gesetzt wird. Das Warten auf eine queued -Methode, die abgeschlossen werden muss, bevor die Steuerung an den Thread zurückgegeben wird, ist genau das, für das Synchronize() bestimmt ist. Synchronize() ist synchron und wartet auf das Beenden der Methode. Queue() ist asynchron, es wartet überhaupt nicht.

    
Remy Lebeau 29.04.2014 09:36
quelle
0

Ich habe dieses Problem behoben, indem ich am Ende meiner Synchronize() -Methode einen Aufruf von Execute() hinzugefügt habe. Dies zwingt den Thread zu warten, bis alle Aufrufe, die mit Queue() hinzugefügt wurden, im Hauptthread abgeschlossen sind, bevor der mit Synchronize() hinzugefügte Aufruf aufgerufen werden kann.

%Vor%     
Nat 14.10.2016 04:35
quelle
-1

Ein paar Gedanken:

  1. FreeOnTerminate ist nicht das Ende der Welt, wenn dein Thread sich selbst löschen soll.
  2. Semaphore lassen Sie eine Zählung aufrechterhalten, sollten Sie das Bedürfnis haben, gibt es solche Konstrukte.
  3. Es gibt nichts, was Sie davon abhält, eigene Warteschlangen-Primitive zu schreiben oder zu verwenden und AllocateHWnd zu verwenden, wenn Sie eine feinkörnige Kontrolle wünschen.
Martin Harvey 26.07.2014 23:10
quelle

Tags und Links