Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Thread .Terminate beim Beenden - Runtime Error 216 (https://www.delphipraxis.net/138516-thread-terminate-beim-beenden-runtime-error-216-a.html)

SvB 11. Aug 2009 23:41


Thread .Terminate beim Beenden - Runtime Error 216
 
Ich habe in einem D2007 Programm sinngemäß folgenden Code:

Delphi-Quellcode:
TmyThread = class(TThread)
  protected
    procedure Execute; override;
  public
    constructor Create(CreateSuspended: Boolean);
    destructor Destroy; override;
  end;

constructor TmyThread.Create(CreateSuspended: Boolean);
begin
  inherited Create(CreateSuspended);
end;

destructor TmyThread.Destroy;
begin
  inherited;
end;

procedure TmyThread.Execute;
begin
  while not Terminated do begin
    // mach irgend etwas, berechne
    self.Suspend;
  end;
end;

.
.
.

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  myThread := TmyThread.Create(True);
  myThread.FreeOnTerminate := True;
  myThread.Resume;   // Starte Thread und laufe einmal durch
end;

procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  myThread.Terminate;
  if myThread.Suspended then begin
    myThread.Resume;
  end;
  // Was kann man hier einbauen um zu prüfen, ob der Thread beendet und zerstört ist
end;
Also der Thread läuft beim Starten einmal durch und legt sich dann selbst schlafen. Unter verschiedenen Umständen in meinem Programm wird der Thread ab und zu mal wieder aufgeweckt. Das funktioniert auch alles wunderbar und ohne Probleme.
Mein Problem ist allerdings, dass ab und zu beim Beenden vom Programm ein Runtime Error 216 kommt. Ich habe dann herausgefunden dass es am Thread liegt, bzw. dass er wohl noch nicht beendet und zerstört ist, das Hauptformular aber dann schon. Wenn ich das Programm über die IDE starte, dann kommt der Fehler fast gar nicht, wenn ich die EXE außerhalb der IDE starte, dann kommt der Fehler ziemlich oft.
Ich habe auch schon folgendes probiert:
Delphi-Quellcode:
procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  myThread.Terminate;
  if myThread.Suspended then begin
    myThread.Resume;
  end;
  myThread.WaitFor;     // <----- neu
end;
aber dann gibts ein Fehler bei WaitFor, dass die Thread ID nicht stimmt (oder so ähnlich).

Wenn ich folgendes mache:
Delphi-Quellcode:
procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  myThread.Terminate;
  if myThread.Suspended then begin
    myThread.Resume;
  end;
  sleep(50);         // kurze Pause
end;
dann gibt es nie einen Fehler, das gefällt mir aber nicht, einfach eine Pause da rein zu hauen.

Folgendes bringt mich auch nicht weiter:
Delphi-Quellcode:
procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  myThread.Terminate;
  if myThread.Suspended then begin
    myThread.Resume;
  end;
  while myThread <> nil do begin     // <----- neu
    sleep(50);
    Application.ProcessMessages;
  end;
end;
Das Problem dabei ist, dass myThread nach dem beenden und zerstören immer noch <> nil ist. Ich habe auch geprüft, dass beim Aufruf von Resume das Execute des thread verlassen wird und Destroy des Thread wird auch durchlaufen.

Habt Ihr eine Idee, was ich tun kann, wenn FreeOnTerminate := True und ich den Thread dann selbst terminiere, ich aber noch prüfen will, ob er wirklich beendet ist.

Danke
Sven

jaenicke 11. Aug 2009 23:59

Re: Thread .Terminate beim Beenden - Runtime Error 216
 
FreeOnTerminate = True in Kombination mit Resume ist eine ganz schlechte Idee...
Insbesondere, weil bei dir nach dem Suspend im Thread nix mehr passiert.

Mehr dazu findest du hier:
http://www.delphipraxis.net/internal....php?p=1064702

SvB 12. Aug 2009 00:24

Re: Thread .Terminate beim Beenden - Runtime Error 216
 
Ja, den Beitrag hatte ich auch heute schon durchgekaut und jetzt aktuell noch mal.
Ich habe dann aus http://qc.embarcadero.com/wc/qcmain.aspx?d=26291 den Fix
Delphi-Quellcode:
// The same fix can be made in user's code at the end of Execute:

TMyThread.Execute;
begin
...
    if FreeOnTerminate and (Suspended=True) then
     repeat Sleep(0); until (Suspended=False);
end;
in meinen Thread eingebaut und jetzt scheint es einwandfrei und ohen Fehler zu laufen.

Diesen Beitrag kann man sich auch noch mal in Bezug auf das Problem ansehen: Thread: Fatal Threading Model!

Ansonsten sehe ich an meinem Code nicht unbedingt ein Problem.

Trotdem schon mal Danke.

Grüße
Sven

sirius 12. Aug 2009 08:09

Re: Thread .Terminate beim Beenden - Runtime Error 216
 
Abgesehen, dass es nicht günstig ist "varBoolean=True" zu prüfen...
Wie soll das bitte zuverlässig funktionieren? Das sieht mir mehr nach einem Wettlauf aus, der mal klappt und mal (auf einem anderen Rechner) nicht.
Und wieso kann man in einem Thread auf suspended abfragen? Entweder das ist true, dann läuft die Abfrage aber auch nicht, oder es ist false dann ist die Bedingung auch false.
:gruebel:

alzaimar 12. Aug 2009 08:42

Re: Thread .Terminate beim Beenden - Runtime Error 216
 
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... :mrgreen:

Luckie 12. Aug 2009 08:48

Re: Thread .Terminate beim Beenden - Runtime Error 216
 
Zitat:

Zitat von alzaimar
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.

Genau deswegen, sollte dies der Thread immer selber tun. Man sagt ihm von aussen nur, dass er sich anhalten oder beenden soll und der Thread entscheidet dann von sich wann er dies tut.

SvB 12. Aug 2009 19:47

Re: Thread .Terminate beim Beenden - Runtime Error 216
 
Klar, nach dem ich mal eine Nacht drüber geschlafen habe, habe ich mich auch dazu entschlossen nicht mit FreeOnTerminate := True zu arbeiten, sondern ich kümmere mich selbst darum, wann der Thread zerstört wird. Da habe ich dann eine bessere Kontrolle, wann was passiert.

Das Suspend habe ich auch in den Thread selbst an das Ende der eigentlichen Arbeit gesetzt, um ihn gezielt schlafen zu legen. Damit habe ich auch die Kontrolle drüber. Wann das Resume aufgerufen wird, sollte dann doch eigentlich egal sein, da ja dann wieder von vorne begonnen wird.

Das mit den Semaphoren muss ich mir dann auch bei Gelegenheit noch mal reinziehen, wenn ich mal ein paar Minuten Luft holen kann.
@alzaimar: Du verwendest das ja auch im WorkerThread

Jetzt habe ich aber noch eine Frage dazu mit diesen Jobs". Ich habe noch einen zweiten Thread den ich noch anpassen muss, der läuft alle 30 Sekunden wieder von vorne los. Stelle ich jetzt über einen Timer alle 30 Sekunden einen neuen Job in die Liste oder stelle ich den Job nur einmal rein und regele dann innerhalb des Jobs das Intervall. Aber wenn er dann ja nie beendet wird, wird auch den nächste Job nicht abgearbeitet????

Jetzt muss ich aber erst mal schauen, dass ich eine neue Softwareversion fertig bekomme, bin schon ein paar Tage überfällig, ansonsten springt mir bald jemand an die Gurgel.

Erst mal Danke. Threads sind schon eine tolle Sache, wenn man es richtig programmiert und es dann auch funktioniert.

Grüße
Sven


Alle Zeitangaben in WEZ +1. Es ist jetzt 15:33 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz