Thread lässt sich nicht freigeben

Dets
Beiträge: 61
Registriert: Di 11. Sep 2007, 16:59
OS, Lazarus, FPC: Ubuntu Maverick (L 0.9.28.2-10, FPC 2.4.0)
CPU-Target: 32Bit
Wohnort: Lage
Kontaktdaten:

Thread lässt sich nicht freigeben

Beitrag von Dets »

Hallo Leute,

also irgendwie stehe ich auf dem Schlauch: kann man unter (k)ubuntu Linux mit Lazarus 0.9.24beta und FPC 2.2.0 einen Thread nicht beenden (unter Win XP funktioniert's!)?

Beispiel gefällig?

Code: Alles auswählen

TSimpleThread = class(TThread)
  private
  protected
    procedure Execute; override;
  public
    constructor Create;
  end;
 
  { TForm1 }
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormCreate(Sender: TObject);
  private
    { private declarations }
    aThread: TSimpleThread;
  public
    { public declarations }
  end; 
 
var
  Form1: TForm1; 
 
implementation
 
constructor TSimpleThread.Create;
begin
     FreeOnTerminate:=True;
     inherited Create(true);
end;
 
procedure TSimpleThread.Execute;
begin
     while not terminated do
     begin
          sleep(500);
          suspend;
     end;
end;
 
{ TForm1 }
 
procedure TForm1.FormCreate(Sender: TObject);
begin
     aThread:=TSimpleThread.Create;
end;
 
procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
     aThread.Terminate;
     aThread.Free;
end;
 
procedure TForm1.Button1Click(Sender: TObject);
begin
     aThread.Resume;
end;
In obigem Quelltext möchte ich eine Thread-Routine (in diesem Fall nur ein Sleep) durch Klicken auf den Button ausführen und dann stoppen, was auch wie gewünscht funktioniert.
Sobald man aber auch nur ein einziges Mal auf den Button geklickt hat, lässt sich das Formular nicht mehr schließen, das Programm hängt im FormClose bei aThread.Free.
Offensichtlich klemmt es bei TThread.WaitFor, denn wenn man ein aThread.WaitFor vor aThread.Free im FormClose einbaut, dann bleibt das Programm an der Stelle hängen.

Hat irgendwer einen funktionierenden Workaround? Oder hab ich etwas wesentliches übersehen?

greetz, Dets ...

Benutzeravatar
theo
Beiträge: 10927
Registriert: Mo 11. Sep 2006, 19:01

Beitrag von theo »

Im FormClose vor dem aThread.Terminate ein
aThread.Resume;
machen, sonst hängt der.

Aber wieso suspendierst du den überhaupt?

Dets
Beiträge: 61
Registriert: Di 11. Sep 2007, 16:59
OS, Lazarus, FPC: Ubuntu Maverick (L 0.9.28.2-10, FPC 2.4.0)
CPU-Target: 32Bit
Wohnort: Lage
Kontaktdaten:

Beitrag von Dets »

theo hat geschrieben:Im FormClose vor dem aThread.Terminate ein
aThread.Resume;
machen, sonst hängt der.
Jau, funktioniert. Nach dem Terminate funktioniert's auch und dann denke ich, dass die Execute-Schleife nicht noch einmal ausgeführt wird.
theo hat geschrieben:Aber wieso suspendierst du den überhaupt?
Das war ja mehr so ein Proof of concept, im eigentlichen Programm sollen Dateien per FTP an verschiedene FTP-Server übertragen werden. Dazu werden die Zugangsdaten und eine Liste mit Dateinamen jeweils an den Thread übergeben und dann per Resume die Übertragung gestartet. Ist er damit durch, wird er suspended, bis neue Daten für einen anderen Server aufbereitet worden sind.

thx, Dets ...

Dets
Beiträge: 61
Registriert: Di 11. Sep 2007, 16:59
OS, Lazarus, FPC: Ubuntu Maverick (L 0.9.28.2-10, FPC 2.4.0)
CPU-Target: 32Bit
Wohnort: Lage
Kontaktdaten:

Beitrag von Dets »

theo hat geschrieben:Im FormClose vor dem aThread.Terminate ein
aThread.Resume;
machen, sonst hängt der.
Nachtrag: es funktioniert nur, wenn der Thread bereits suspended ist. Ansonsten hängt der Thread wieder. :(

Dets ...

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Beitrag von mschnell »

Code: Alles auswählen

procedure TForm1.FormClose%u28Sender%u3a TObject; var CloseAction%u3a TCloseAction%u29;
begin
     aThread.Terminate;
     aThread.Free;
end;
Der Thread wird gefreef (d.h. sein Memory freigegeben), obwohl er noch läuft (er ist ja nach Terminate nicht sofort beendet).


Wenn Du

Code: Alles auswählen

procedure TSimpleThread.Execute;
begin
     while not terminated do
     begin
          sleep(500);
     end;
end;
(ohne suspend) machst, und aThread.FreeOnTerminate:=true; definierst und dann aThread.terminate aufrufst sollte es klappen.

Allerdings darfst Du nach aThread.terminate das Programm nicht schließen sondern musst warten, bis der Tread wirklich beendet ist, weil aThread ja vermutlich das Form als Parent hat und deshalb mit dem Form zwangs-gefreed wird.

-Michael

Dets
Beiträge: 61
Registriert: Di 11. Sep 2007, 16:59
OS, Lazarus, FPC: Ubuntu Maverick (L 0.9.28.2-10, FPC 2.4.0)
CPU-Target: 32Bit
Wohnort: Lage
Kontaktdaten:

Beitrag von Dets »

mschnell hat geschrieben:Der Thread wird gefreef (d.h. sein Memory freigegeben), obwohl er noch läuft (er ist ja nach Terminate nicht sofort beendet).
Das stimmt so nicht. In TThread.Destroy wird erst auf das Ende des Threads gewartet, bevor die Thread-Instanz freigegeben wird:

Code: Alles auswählen

if not FFinished then
  begin
    Terminate;
    if (FInitialSuspended) then
      Resume;
    WaitFor;
  end;
mschnell hat geschrieben: Wenn Du

Code: Alles auswählen

procedure TSimpleThread.Execute;
begin
     while not terminated do
     begin
          sleep(500);
     end;
end;
(ohne suspend) machst, und aThread.FreeOnTerminate:=true; definierst und dann aThread.terminate aufrufst sollte es klappen.
Dann könnte ich ja auch gleich

Code: Alles auswählen

procedure TSimpleThread.Execute;
begin
        sleep(500);
end;
machen. Und müsste dann für jeden Durchlauf des Threads eine eigene Thread-Instanz erzeugen. Das ist aber eigentlich nicht das, was ich wollte.
mschnell hat geschrieben:Allerdings darfst Du nach aThread.terminate das Programm nicht schließen sondern musst warten, bis der Tread wirklich beendet ist, weil aThread ja vermutlich das Form als Parent hat und deshalb mit dem Form zwangs-gefreed wird.
Ein Thread hat keinen Parent. Ich muss auch nicht warten, bis der Thread beendet ist, weil das TThread.Destroy selbst macht. Nur leider nicht richtig.

Dets ...

Benutzeravatar
theo
Beiträge: 10927
Registriert: Mo 11. Sep 2006, 19:01

Beitrag von theo »

Und was war noch die Frage?

So geht doch oder nicht?

procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
aThread.Terminate;
if aThread.Suspended then aThread.Resume;
end;

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Beitrag von mschnell »

Dets hat geschrieben:
mschnell hat geschrieben:Der Thread wird gefreef (d.h. sein Memory freigegeben), obwohl er noch läuft (er ist ja nach Terminate nicht sofort beendet).
Das stimmt so nicht. In TThread.Destroy wird erst auf das Ende des Threads gewartet, bevor die Thread-Instanz freigegeben wird:

Code: Alles auswählen

if not FFinished then
  begin
    Terminate;
    if (FInitialSuspended) then
      Resume;
    WaitFor;
  end;
Hmm.

Da Thread.Free (also im Endeffekt Destroy) nicht vom Thred, sondern vom Hauptoprogramm aufgerufen wird, bleibt das Hauptprogramm also in Waitfor hängen, bis der Thread sich selbst beendet (In Deinem Beispiel also bis zu 500 msek, in anderen Beispielen ewig).

Das ist ein so nicht unbedingt zu erwartender Nebeneffekt, der ordentlich dokumentiert sein sollte.

-Michael

Dets
Beiträge: 61
Registriert: Di 11. Sep 2007, 16:59
OS, Lazarus, FPC: Ubuntu Maverick (L 0.9.28.2-10, FPC 2.4.0)
CPU-Target: 32Bit
Wohnort: Lage
Kontaktdaten:

Beitrag von Dets »

mschnell hat geschrieben: Da Thread.Free (also im Endeffekt Destroy) nicht vom Thred, sondern vom Hauptoprogramm aufgerufen wird, bleibt das Hauptprogramm also in Waitfor hängen, bis der Thread sich selbst beendet (In Deinem Beispiel also bis zu 500 msek, in anderen Beispielen ewig).
Das wäre dann ja so wie beabsichtigt. Nein, stattdessen bleibt Free (wegen WaitFor) bis zum jüngsten Tag hängen - auch in meinem Thread, wo es eigentlich nur 500ms warten sollte.

Dets ...

mschnell
Beiträge: 3444
Registriert: Mo 11. Sep 2006, 10:24
OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
CPU-Target: X32 / X64 / ARMv5
Wohnort: Krefeld

Beitrag von mschnell »

Dets hat geschrieben:
mschnell hat geschrieben: Das wäre dann ja so wie beabsichtigt. Nein, stattdessen bleibt Free (wegen WaitFor) bis zum jüngsten Tag hängen - auch in meinem Thread, wo es eigentlich nur 500ms warten sollte....
Wenn Dein Thread ein suspend auf sich selbst macht, ist es doch kein Wunder, dass Waitfor nicht fertig wird. Ich vermute FInitialSuspended ist auch nur true, wenn der Stread suspended gestartet wurde, nicht wenn er nachträglich durch einen anderen Thread oder von sich selbst suspended wurde.

Ich habe auch immer noch nicht verstanden, was das (selbst-) suspend in Deinem Thread soll.

-Michael

Dets
Beiträge: 61
Registriert: Di 11. Sep 2007, 16:59
OS, Lazarus, FPC: Ubuntu Maverick (L 0.9.28.2-10, FPC 2.4.0)
CPU-Target: 32Bit
Wohnort: Lage
Kontaktdaten:

Beitrag von Dets »

mschnell hat geschrieben:Ich habe auch immer noch nicht verstanden, was das (selbst-) suspend in Deinem Thread soll.
Ist doch ganz einfach: der Thread bekommt Daten, wird gestartet (=Resume), verarbeitet die Daten(=Sleep) und wenn er fertig ist, wartet er (=Suspend) darauf, dass das Hauptprogramm die Daten abholt und neue liefert. Dann beginnt das Spiel von vorn.
Ist das eine so ungewöhnliche Art einen Thread zu benutzen?

Dets ...

Benutzeravatar
theo
Beiträge: 10927
Registriert: Mo 11. Sep 2006, 19:01

Beitrag von theo »

Dets hat geschrieben: Ist das eine so ungewöhnliche Art einen Thread zu benutzen?
Kommt auf die Verwendung an. Wenn es so ist wie es klingt, würde ich mir das Leben möglichst einfach machen und für jeden Job einen neuen Thread bauen, der nach der Arbeit wieder verschwindet.

Wenn du allerdings die jew. Verbindungen zu den FTP Servern halten willst, brauchst du wohl eher eine art Thread Pool.

Christian
Beiträge: 6079
Registriert: Do 21. Sep 2006, 07:51
OS, Lazarus, FPC: iWinux (L 1.x.xy FPC 2.y.z)
CPU-Target: AVR,ARM,x86(-64)
Wohnort: Dessau
Kontaktdaten:

Beitrag von Christian »

Ich finds gerad nicth aber irgendwie bin ich der meinung Threads selbst Suspenden ist pfui. Suspend ist nur dafür gedacht den Thread von extern anzuhalten. Aus dem Thread heraus sollte man eine Critical Section o.ä. nehmen. Ich weiß es aber wie gesagt nicht mehr genau aber irgendwas war da.
W.m.k.A.h.e.m.F.h. -> http://www.gidf.de/

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6857
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Beitrag von af0815 »

Sauber wäre auch das schlafenlegen über WaitForSingeObject (eine Info dazu). Damit kann man den Thread schlafen legen und über einen Event aufwecken, zusätzlich ist eine Zeitangabe möglich wie lange er schlafen soll. Kann man so betrachten wie ein Sleep mit externer Weckmöglichkeit.

Ich weis nur nicht, ob das bei Lazarus/FPC schon iin den Bibliotheken ist.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Dets
Beiträge: 61
Registriert: Di 11. Sep 2007, 16:59
OS, Lazarus, FPC: Ubuntu Maverick (L 0.9.28.2-10, FPC 2.4.0)
CPU-Target: 32Bit
Wohnort: Lage
Kontaktdaten:

Beitrag von Dets »

Christian hat geschrieben:Ich finds gerad nicth aber irgendwie bin ich der meinung Threads selbst Suspenden ist pfui. Suspend ist nur dafür gedacht den Thread von extern anzuhalten.
Wenn man einen Thread von außen suspended, dann weiß man doch gar nicht, an welcher Stelle im Code er sich gerade befindet. Das ist doch hochgradig abenteuerlich. Der Fall hingegen, in dem der Thread weiß, wann er sich selbst suspenden kann, dürfte wesentlich häufiger vorkommen und deswegen nicht pfui sein.

Dets ...

Antworten