Patito hat geschrieben:
Du machst da folgende Sachen:
1) Einem FreeOnTerminate-Thread sein Kill-Event senden und danach noch auf seinen Speicher zugreifen
2) Die Probleme verschleiern mit:
a) Busy-Waits
b) Sleep()
c) Katastrophen wie:
Code: Alles auswählen
Application.processMessages; // Here too! Very important!
Was den Programmablauf dann endgültig ruiniert.
Zu 1: Da hast Du Recht, das habe ich auch schon geändert. Ich bin nur noch nicht dazu gekommen, das hier zu korrigieren.
Code: Alles auswählen
procedure TWorldThread.lifeTerminated(Sender: TObject);
begin
InterLockedDecrement(FThreadsRunning);
// FList.remove(Sender); // dangerous! unknown position in 'FList'!
end;
procedure TWorldThread.terminateAllLifes;
var i, co:integer; aThread:TLifeThread;
begin
while FLocked do sleep(1);
try
FLocked := true;
for i := FList.count - 1 downto 0 do
begin
aThread := TLifeThread(self.FList[i]);
aThread.freeOnTerminate := true;
// works on Windows but not on Mac / Linux:
// aThread.terminate;
// aThread.waitFor; // -> exception! ThreadExample in Lazarus Wiki is wrong:
// http://wiki.lazarus.freepascal.org/Manager_Worker_Threads_System" onclick="window.open(this.href);return false;
// works on both (Windows / Unix)
basiceventSetEvent(TLifeThread(self.FList[i])._killEvent);
sleep(1);
end;
// Don't implement a alternative to 'waitFor' here!
// Test on 'FThreadsRunning > 0' in a while loop in the mainForm instead!
// lifeTerminated will be reached on a 'peekMessage' in the applications idle
// loop. But this method works only on Windows. Therefore you have to do
// something in the main application before the 'onTerminate'-event from a
// terminated thread will be reached.
FList.clear;
finally
FLocked := false;
end;
end;
Zu 2 - Busy-Waits) Da behalte ich wenigsten den Überblick im Gegensatz zu CriticalSection, Semaphoren, Mutexe und was es da sonst noch für Zeugs gibt. Irgendwo habe ich auch gelesen, dass das gar kein so schlechter Programmierstil ist. Aber man liest ja vieles, das geb ich zu. Eigentlich funktionieren diese Konstrukte ziemlich gut und bewahren das Programm vor jede Menge Zugriffskonflikten. Aber Du könntest insofern Recht haben, als ich vor allem wohl noch das Hochzählen der Threadvaribalen 'Value := Value + 1' bzw 'Value=0' in 'execute' mit EnterCriticalSection, etc absichern muss.
Zu 2 - Sleep) Ok, die habe ich auch nur drin, entweder für timeouts oder weil ich sie vergessen habe

Vielleicht auch, weil ich mir was davon versprochen haben, _killEvents nicht so schnell hintereinander abzuschicken. Aber zugegeben, etwas intuitiv gesetzt.
zu 2 - Application.processMessages) Das ist das zentrale Problem, das ich habe. Ich habe meiner Meinung nach nur eine Möglichkeit, festzustellen, ob das Objekt terminiert wurde (was ich ja in FormClose unbedingt wissen muss) und das ist das 'onTerminate'-Event. Das wird aber erst dann ausgeführt, wenn sich die Gui des Hauptprogramms wieder in der Idle-Loop befindet. In Windows dagegen brauche ich nicht erst im Hauptprogramm 'Application.processMessages' im Hauptformular (bzw. dort in einer Whileschleife darauf warten, dass FThreadsRunning = 0 wird), weil ich da mit waitFor oder analogen Methoden arbeiten kann, die ja auch nichts anderes machen, als die interne Botschaften abzuarbeiten. Aber unter Unix kann ich das nicht. Da muss ich quasi die Botschaftsverwaltung des jeweiligen Widgetsets "mißbrauchen", damit überhaupt vom Thread das 'onTerminate' Event ausgelöst wird und ich dann über den Zähler FThreadsRunning = 0 prüfen kann, ob alle Threads beendet sind.
Dass das Quick & Dirty ist, weiß ich auch. Aber wenn mir einer zeigt, wie ich unter Unix korrekt waitFor oder ähnliches in FormClose verwenden kann, geh ich in Sack und Asche!
In sämtlichen Beispielen, die man zu Threads findet, werden überhaupt keine Maßnahmen getroffen, die laufenden Threads im FormClose gezielt zu beenden.
Man kann natürlich Thread-Programme so schreiben, dass keine Threads mehr laufen, wenn der User das Formular schließt. Man kann eventuell auch Threads so schreiben, dass sie gar keine Objekte enthalten, die aufgeräumt werden müssen. Solche Threads verwalten dann Datenobjekte in irgendwelchen Listen des Hauptthreads und bestehen dann quasi nur aus Programmcode, nicht aus Daten und der Zugriff darauf wird mit CriticalSection abgesichert.
Aber wenn es nicht verboten ist, dass Threads parallel laufen und auf Aufgaben warten, anstatt sich nach jeder Task aufzulösen und wenn es nicht verboten ist, dass Threads auch Datenobjekte enthalten können, dann muss es auch eine Möglichkeit geben, festzustellen, dass Threads definitiv terminiert sind. In Windows geht das mit 'waitFor' und ähnlichem. In Unix funktiert das aber nicht
Übrigens wird in dem Beispielcode im Lazarus-Wiki (s. Posting weiter oben) das Programm auch nicht korrekt beendet. Das Beispiel funktioniert nur unter Windows korrekt. Unter Linux wird an der Stelle mit 'waitFor' eine Exception ausgelöst, die im frei laufenden Programm wahrscheinlich nur zufällig keine Folgen hat.
Ich hab das geänderte Programm hier nochmal im Anhang eingestellt (wer mal probieren möchte).
Nachtrag: Die 'FLocks:=true' und die 'sleep(1)' kann man auch überall ausixen. Dann läufts auch. Hab ich aber nur unter Windows getestet jetzt.