Das kontrollierte Beenden von Threads kann Einem schon mal den Schlaf rauben. Ich verzichte grundsätzlich auf Resume/Suspend und arbeite stattdessen lieber mit Synchronisationsobjekten (Semaphoren). Weiterhin verzichte ich auf 'FreeOnTerminate' und gebe Threads immer kontrolliert frei. Tut man das nicht, kann es sein, das einem die Applikation beim Programmende um die Ohren fliegt. Im Finalization-Abschnitt von 'Classes.Pas' werden nämlich Resourcen freigegeben, die von der Terminierungssequenz eines Threads benötigt werden.
Ich schreibe mir immer eine Stop-Methode zu meinen Threads, damit der Thread kontrolliert terminiert. Danach kann ich den Thread beruhigt freigeben.
Diese Stop-Methode macht Folgendes:
1. "Terminate" aufrufen. Damit wird ja nur das 'Terminated'-Flag gesetzt.
2. Sicherstellen, das die Execute-Methode terminiert.
3. Mit "WaitFor" warten, bis die Terminierungssequenz des Threads abgearbeitet ist.
Da ich mit Semaphoren arbeite, sieht die Execute-Methode immer gleich aus;
Delphi-Quellcode:
Procedure TMyThread.Execute;
Begin
// Lokale Resourcen initialisieren, ggf. CoInitialize aufrufen, wenn mit COM gearbeitet wird
Try
While Not Terminated
Do Begin
WaitForSingleObject(fSemaphore,INFINITE);
If Terminated
Then Break;
// Arbeit durchführen. Meist einen Job aus einer Queue holen und abarbeiten
End;
Finally
// Lokale Resourcen wieder freigeben
End
End;
Den Thread füttere ich mit Arbeit über eine AddJob-Methode, die den Job in die Queue schiebt und die Semaphore per 'ReleaseSemaphore' anschalte.
Damit reicht es, unter (2) 'ReleaseSemaphore' aufzurufen, denn dann wacht der Thread auf, merkt das er terminieren soll und macht das auch.
Delphi-Quellcode:
Procedure TMyThread.Stop;
Begin
Terminate; // Flag setzen
ReleaseSemaphore(fSemaphore,1,nil); // 'Arbeit beenden' anfordern
WaitFor; // Warten, bis Arbeit beendet ist
End;
Falls mein Thread noch mitten im Abarbeiten von Jobs steckt, die sich in der Queue angesammelt haben, macht das nichts. Er wird in jedem Fall nach Beendigung des aktuellen Jobs terminieren. Will ich das nicht, habe ich einen speziellen 'Ende-Job', den ich in die Queue schiebe. Dann werden zunächst alle Jobs abgearbeitet, dann kommt der Ende-Job und die 'Execute'-Methode hört auf.
Delphi-Quellcode:
Procedure TMyThread.Stop;
Begin
Add (TEndThreadJob.Create); // Ende-Job in die Queue schieben
WaitFor; // Warten, bis Arbeit beendet ist
End;
Von außen sieht die Arbeit mit meinen Threads also immer gleich aus: Erzeugen, mit Job füttern, Stop aufrufen, freigeben.
Ach, warum ich nie mit Release/Suspend arbeite? Ich stell mir das so vor: Ich habe ja einen Arbeiter, der im Hintergrund Sachen für mich erledigt. Mit Suspend brate ich ihm eins über, sodaß er auf der Stelle ohnmächtig wird. Was ist aber, wenn er gerade in einer wichtigen Sache steckt? Blöd gelaufen. Ich muss also immer auf der Hut sein, ob und wann ich ihm eins verpasse.
Es geht natürlich so, speziell wenn nur der Thread selbst sich eins vor die Rübe knallt, aber mir liegen diese Semaphoren einfach besser. Bildlich gesehen ackert mein Arbeiter im Nebenraum und ich schiebe ihm durch ein Fenster Zettel zu, auf denen steht, was er bei als nächstes zu tun hat. Irgendwie humaner, finde ich.
Na ja. Arbeiter eingesperrt im Nebenraum, das ruft auch die Gewerkschaft auf den Plan...