Entwickler-Ecke

Sonstiges (Delphi) - MyThread.Terminate und FreeAndNil(MyThread) ?


crowley - Fr 23.06.06 10:33
Titel: MyThread.Terminate und FreeAndNil(MyThread) ?
Hallöli

ich habe ein irritierendes Problem: Also... ich möchte in einem eigenen Thread ein Form anzeigen, dass mir mittels Fortschrittsbalken anzeigt, dass "etwas" passiert... Schlussendlich ist es eine Progressbar, die auf und ab läuft. Prinzipiell funktioniert das auch hervorragend. Jetzt habe ich aber Probleme im destructor. Wenn ich im Hauptprogramm schreibe


Delphi-Quelltext
1:
2:
  MyThread.Terminate;
  MyThread := nil;


habe ich keinerlei Problem. Wenn ich allerdings - in welcher Form auch immer - den destructor aufrufe, dann hängt sich die Anwendung auf...


Delphi-Quelltext
1:
2:
  MyThread.Terminate;
  FreeAndNil(MyThread); { macht keinen Unterschied zu MyThread.Free + MyThread := nil}


Hier zuerst mal der Quellcode meines Thread- Objektes:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
unit GnProgressThread;

interface

uses
  Classes, GnProgressForm;

type
  TProgressThread = class(TThread)
  private
    FProgForm            : TProgressForm;

    procedure ShowProgress;
  public
    constructor Create(CreateSuspended: Boolean);
    destructor Destroy; override;

    procedure Execute; override;
  end;

implementation

{ TProgressThread }

uses Forms, SysUtils;

constructor TProgressThread.Create(CreateSuspended: Boolean);
begin
  inherited;

  FreeOnTerminate := true;
end;

destructor TProgressThread.Destroy;
begin
  if not Terminated then begin
    Terminate;
    
    if Suspended then
      Resume;

    WaitFor;
  end;

  if Assigned(FProgForm) then begin
    FProgForm.Close;
    FreeAndNil(FProgForm);
  end;

  inherited Destroy;
end;

procedure TProgressThread.Execute;
begin
  Synchronize(ShowProgress);
end;

procedure TProgressThread.ShowProgress;
begin
  FProgForm := TProgressForm.Create(nil);

  try
    FProgForm.Show;

    while not Terminated do begin
      if FProgForm.ProgressBar.Position < FProgForm.ProgressBar.Max then
        FProgForm.ProgressBar.Position := FProgForm.ProgressBar.Position + 1
      else 
        FProgForm.ProgressBar.Position := FProgForm.ProgressBar.Min + 1;

      Sleep(20);
      Application.ProcessMessages;
    end;
  finally
    FProgForm.Close;
    FreeAndNil(FProgForm);
  end;
end;

end.


Wenn der destructor so aufgerufen wird, bleibt er an dem WaitFor hängen... der Verzicht auf FreeOnTerminate machte keinen Unterschied...

In meinem MainForm habe ich einen Button mit folgenden Code integriert:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
procedure TTestMainForm.Button1Click(Sender: TObject);
begin
  if not Assigned(FThread) then begin
    FThread        := TProgressThread.Create(false);
  end else begin
    FThread.Terminate;       { <--- funktioniert}
    // FThread.Free;         { <--- funktioniert nicht}   
    // FreeAndNil(FThread);  { <--- funktioniert nicht}
    FThread := nil;          { <--- funktioniert, daher brauch ich das FreeOnTerminate}
  end;
end;


Hat da jemand eine Idee, was da verkehrt ist ?

C.


digi_c - Fr 23.06.06 15:49

Ich habe noch nichts groß mit Threads gemacht aber die VCL und die Forms sind doch nicht threadsave also musst du vielleicht mit Critical Sections arbeiten?


Grendel - Fr 23.06.06 16:02

Du musst nach dem Aufruf von "Terminate" noch mit "WaitFor" auf die Beendigung des Thread warten. Terminate beendet den Thread nicht sofort, sondern setzt die Eigenschaft "Terminated" auf True und signalisiert dem Thread dadurch, daß er sich demnächst doch beenden möge. WaitFor kehrt dann zurück, wenn der Thread wirklich durchgelaufen ist.
Danach sollte Free() auch keinen Fehler mehr werfen.

Bis neulich ...

edit: Hab Blödsinn geschrieben. Wenn Du FreeOnTerminate auf True setzt darfts Du natürlich nicht selber versuchen den Thread freizugeben. Nach dem Aufruf von Terminate und WaitFor genügt es, der Variablen einfach nil zuzuweisen. Mit Free bzw. FreeAndNil versuchst Du ja sonst ein Objekt freizugeben, daß garnicht mehr existiert.
Nur wenn FreeOnTerminate False ist muss Du selber Free aufrufen.


crowley - Mo 26.06.06 08:46

@Grendel... das Problem besteht im WaitFor... an dieser Stelle bleibt das Programm hängen... und ich habe keine Ahnung warum... *himpf*... ich habe das ganze mit FreeOnTerminate und auch ohne getestet... es macht keinen Unterschied... *seufz*


@digi_c... Wenn ich den Zugriff auf die VCL im Synchronize mache, brauche ich keine CriticalSections... das ist dann darin schon ausreichend gekapselt...


Grendel - Mo 26.06.06 09:20

Du darfst das WaitFor nicht innerhalb des Threads aufrufen. Schmeiß den ganzen Kram aus dem destructor des Threads:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
  if not Terminated then begin  
    Terminate;  
      
    if Suspended then  
      Resume;  

 
    WaitFor;  
  end;


Um den Thread zu Beenden rufst Du dann folgendes auf:

Delphi-Quelltext
1:
2:
3:
4:
FThread.Terminate();
FThread.WaitFor();
FThread.Free();   // <- nur bei FreeOnTerminate = False
FThread := nil;


Bis neulich ...


crowley - Mo 26.06.06 09:29

werd ich mal versuchen, danke dir ;)

hatte mich bei der implementation an den destructor in der (originalen) TThread-Klasse gehalten... da steht das WaitFor auch im destructor *gg*