Entwickler-Ecke

Sonstiges (Delphi) - Speicherleck bei einer einfachen Thread


Geri - Sa 02.07.11 07:51
Titel: Speicherleck bei einer einfachen Thread
Hallo zusammen

Ich habe eine Anwendung mit einer Thread. Trotz erfolgreichem Aufruf von Terminate erhalte ich mit memchk am Ende eine Meldung, dass die Thread 68 byte nicht freigegeben hat. Ich habe mal ein ganz kurzes Programm geschrieben, bei dem das Problem immer noch auftaucht. Es hat zwei Buttons. Einen um den Thread zu starten, einen um ihn vorzeigig zu beenden. Nach Beendigung der Thread wird zusätzlich ein Event onTerminate aufgerufen. Diese Methode wird auch wirklich aufgerufen. Ich habe mit Delphi 2007 und Delphi XE probiert.


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:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
unit uTestManCursor;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    btnCreate: TButton;
    btnTerminate: TButton;
    Memo1: TMemo;
    procedure btnCreateClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure btnTerminateClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
     FThread:TThread;
     procedure EnableButtons();
     procedure OnTerminate(Sender: TObject);
  end;

  TMyThread=class(TThread)
  private
    FTimeToWork:integer;
  protected
    procedure Execute;override;
  public
    constructor Create(TimeToWork:integer);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

constructor TMyThread.Create(TimeToWork: integer);
begin
  FTimeToWork:=TimeToWork;
  inherited Create(True);
end;


procedure TMyThread.Execute;
var
  T:Integer;
begin
  t:=FTimeToWork;
  Form1.Memo1.Lines.Add('Begin execution');
  while not Terminated and (t>0do
  begin
    Form1.Memo1.Lines.Add(format('Remaining %5.2f%%',[t/FTimeToWork*100]));
    Sleep(500);
    dec(t,500);
  end;
  if Terminated then
  Form1.Memo1.Lines.Add('Terminated by user');
  Form1.Memo1.Lines.Add('Finish execution');
end;


procedure TForm1.EnableButtons();
begin
  btnCreate.Enabled:=not Assigned(FThread);
  btnTerminate.Enabled:= Assigned(FThread);
end;


procedure TForm1.btnCreateClick(Sender: TObject);
  begin
  FThread:=TMyThread.Create(5000);
  FThread.OnTerminate:=OnTerminate;
  EnableButtons();
  FThread.Resume;
end;

procedure TForm1.FormShow(Sender: TObject);
begin
  EnableButtons;
end;


procedure TForm1.btnTerminateClick(Sender: TObject);
begin
  FThread.Terminate;
end;


procedure TForm1.OnTerminate(Sender: TObject);
begin
  FThread:=nil;
  EnableButtons();
end;

end.


Habt ihr vielleicht eine Idee, wo hier das Problem liegen könnte? Memchk als auch die bei Delphi eingebaute Speicherübewachung liefern die Meldung eines Lecks.

Danke für euere Hilfe und beste Grüsse

Geri


haentschman - Sa 02.07.11 08:33

Guten Morgen... :wave:

Delphi-Quelltext
1:
2:
3:
if Terminated then
  Form1.Memo1.Lines.Add('Terminated by user');
  Form1.Memo1.Lines.Add('Finish execution');

- du greifst aus dem Thread heraus auf dein Form1 zu. Das geht nicht gut. Stichwort: Synchronize
Ein Prinzipbeispiel mit freunlichen Grüßen von der DP... wenn sie den Diebstahl bemerkt haben... :zwinker:

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:
type
  MeinForm = class(TMeinForm)
    lblThreadText: TLabel;
  private
    procedure StarteDenThread;
  end;

type
  Thread = class(TThread)
  private
    Form: TMeinForm;
    procedure MachVCLSachen;
  protected
    procedure Execute; override;
  public
    constructor Create(Form: TMeinForm);
  end;

procedure MeinForm.StarteDenThread;
begin
  Thread.Create(Self);
end;

constructor Thread.Create(Form: TMeinForm);
begin
  inherited Create(False);
  Self.Form := Form;
end;

procedure Thread.Execute;
begin
  // Kram machen
  Synchronize(MachVCLSachen);
  // Kram machen
end;

procedure Thread.MachVCLSachen;
begin
  Form.lblThreadText := 'Der Thread lässt grüßen!';
end;


PS: Ich denke, daß Deutsch nicht deine Muttersprache ist. Du kannst auch in Englisch schreiben wenn es für dich einfacher ist.


Kha - Sa 02.07.11 08:59

user profile iconhaentschman hat folgendes geschrieben Zum zitierten Posting springen:
PS: Ich denke, daß Deutsch nicht deine Muttersprache ist.
Sagst du das zu jedem Österreicher :lol: ? Ich würde mit solchen Vermutungen vorsichtig sein, nur weil in einem sonst nicht zu beanstandenen Text jemand den Genus von "Thread" anders gewählt hat, als du es tun würdest :gruebel: .


haentschman - Sa 02.07.11 09:16

Ähmm... das war nicht böse gemeint. :oops: Tschuldigung, Sorry... ich hab nicht ins Profil geguckt.
Für mich las sich das beim Überfliegen wie ein automatisch übersetzter Text.
Zitat:
Ich habe mit Delphi 2007 und Delphi XE probiert.

Vieleicht war es auch nur zu früh für mich...


Teekeks - Sa 02.07.11 12:50

user profile iconGeri hat folgendes geschrieben Zum zitierten Posting springen:

Delphi-Quelltext
1:
2:
3:
  public
    { Public declarations }
     FThread:TThread;

Das sollte doch eher so heißen:

Delphi-Quelltext
1:
2:
3:
  public
    { Public declarations }
     FThread:TMyThread;

Oder?


SvenAbeln - Sa 02.07.11 15:08

Du gibst den Thread doch auch nirgendwo frei, da fehlt ein

Delphi-Quelltext
1:
FThread.free;                    
oder

Delphi-Quelltext
1:
FThread.FreeOnTerminate := True;                    


Boldar - Sa 02.07.11 22:48

user profile iconTeekeks hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconGeri hat folgendes geschrieben Zum zitierten Posting springen:

Delphi-Quelltext
1:
2:
3:
  public
    { Public declarations }
     FThread:TThread;

Das sollte doch eher so heißen:

Delphi-Quelltext
1:
2:
3:
  public
    { Public declarations }
     FThread:TMyThread;

Oder?

Das ist in diesem Fall egal, Stichwort Polymorphismus. Tendenziell würde ich es sogar eher so wie hier vorziehen, da dann die Threadklasse leicht ersetztwerden kann, nämlich im Konstruktor der Form.
Was mich eher wundert, ist das Konstrukt im Create der Threadklasse, denn bei

Delphi-Quelltext
1:
2:
3:
4:
5:
constructor TMyThread.Create(TimeToWork: integer);
begin
  FTimeToWork:=TimeToWork;
  inherited Create(True);
end;

würde ja erst ein Feld gesetzt, bevor der Thread überhaupt erstellt wird...
Evtl. wird da schon der Standard-Konstruktor aufgerufen, und du rufst dann Create nochmals auf, wobei das alte Objekt im Nirwana landet? Du kannst ja vielleicht einfach den alten Konstruktor benutzen und dann FTimeToWork von aussen setzen (dann als property umbauen).


jaenicke - So 03.07.11 13:47

user profile iconBoldar hat folgendes geschrieben Zum zitierten Posting springen:
Evtl. wird da schon der Standard-Konstruktor aufgerufen, und du rufst dann Create nochmals auf, wobei das alte Objekt im Nirwana landet? Du kannst ja vielleicht einfach den alten Konstruktor benutzen und dann FTimeToWork von aussen setzen (dann als property umbauen).
Nein, das ist genau der richtige Weg so...
Es wird zuerst das Feld gesetzt, damit der Wert in Execute schon zur Verfügung steht und dann wird der geerbte Konstruktor aufgerufen, der den Thread startet.
Auf die Weise kann der Thread auch sofort loslaufen und muss nicht pausiert erstellt werden. (Was er hier aber dennoch wird, deshalb geht es hier auch als Property.)

Wenn du den Wert erst hinterher setzt, musst du ja sicherstellen, dass Execute nicht vorher schon ausgeführt wird... (z.B. wie hier durch pausiertes Starten)


Geri - Mo 04.07.11 20:42

Hallo zusammen

Vielen Dank für eure ausführliche Hilfe! Mit FreeOnTerminate hatte ich getestet. Wahrscheinlich hatte ich aber ein Brett vor dem Kopf...

Beste Grüsse und Danke nochmals!

Geri
PS: "die Thread" war nicht Absicht sondern ein Tippfehler.