![]() |
Freigabe Thread
Hallo zusammen,
ich habe folgenden Code (total abgespeckt auf ein Minimum). Ich create einen Thread mit FreeOnTerminate=False, der tut irgendwas, wird fertig und wird dann wieder gefreet. Trotzdem habe ich beim Beenden ein Speicherleck von dem Objekt obwohl das Destroy vom Thread vorher durchgelaufen wird beim Destroy der Form, dass Objekt auch Nil ist. Könnt ihr mir bitte weiterhelfen?
Delphi-Quellcode:
unit Unit1;
interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Generics.Collections, Vcl.StdCtrls; type TSammelListe = Class(TList<TGraphicControl>); TAnalyseThread = class(TThread) private FList : TSammelListe; protected procedure Execute; override; public Status : AnsiString; OnReady : TNotifyEvent; constructor Create(CreateSuspended : Boolean); destructor Destroy; override; end; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); procedure FormDestroy(Sender: TObject); private { Private-Deklarationen } AnalyseThread : TAnalyseThread; procedure OnAnalyseReady(Sender: TObject); public { Public-Deklarationen } end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); begin if Assigned(AnalyseThread) then begin AnalyseThread.Terminate; FreeAndNil(AnalyseThread); end; AnalyseThread := TAnalyseThread.Create(True); if Assigned(AnalyseThread) then begin AnalyseThread.OnReady := OnAnalyseReady; AnalyseThread.Start; end; end; procedure TForm1.OnAnalyseReady(Sender: TObject); begin //Auswertung des Daten... if Assigned(AnalyseThread) then begin AnalyseThread.Terminate; FreeAndNil(AnalyseThread); end; end; procedure TForm1.FormDestroy(Sender: TObject); begin if Assigned(AnalyseThread) then begin AnalyseThread.Terminate; FreeAndNil(AnalyseThread); end; end; { TAnalyseThread } constructor TAnalyseThread.Create(CreateSuspended: Boolean); begin inherited Create(CreateSuspended); FList := TSammelListe.Create; FreeOnTerminate := False; end; destructor TAnalyseThread.Destroy; begin if Assigned(FList) then begin FList.Clear; FreeAndNil(FList); end; inherited Destroy; end; procedure TAnalyseThread.Execute; begin inherited; //Tue irgendwas bis fertig... if Assigned(OnReady) then OnReady(Self); end; end. |
AW: Freigabe Thread
Also per se stimmt mit TSammelListe eigentlich alles. (im Create erstellen und im Destroy freigeben ... beides auf der selben Ebene ist immer gut)
Bist du sicher, dass das Speicherleck "dieses" Objekt ist und nicht irgendwas Anderes? Was aber niemand verbieter, ins Create und Destroy der TSammelListe einen Haltepunkt oder Logmeldung einzufügen und zu schauen ob und wie oft das erstellt/freigegeben wird. Was du eventuell noch bedenken mußt, TThread.Create wird immer im erstellenden Thread ausgeührt, während bei FreeOnTerminate das Thread.Destroy immer im Thread abläuft. Und bei einem externen .Free das Thread.Destroy ebenfalls in einem anderen Thread. Aber manche Dinge müssen/dürfen (nur) im selben Thread genutzt werden, wo sie erstellt wurden. -> Hier geht also nur TComponent.Create und Component.Free mit einem Try-Finally im Execute. Und was ist TSammelListe? "Normalerweise" machen Listen im Free automatisch ein Clear, somit würde ein Free ausreichen.
Delphi-Quellcode:
[EDIT] Jupp, die Liste gibt "ihre" Items frei, aber nicht die darin gespeicherten Objektreferenzen ... siehe Der schöne Günther
destructor TAnalyseThread.Destroy;
begin FList.Free; // oder FreeAndNil(FList); inherited; end; |
AW: Freigabe Thread
Dein Thread ist in Ordnung, das Problem ist dein
Delphi-Quellcode:
. Die Liste kannst du so oft clearen und freigeben wie du willst, die gibt ihre enthaltenen Elemente nicht selbst frei.
TList<TGraphicControl>
Was du stattdessen willst ist eine
Delphi-Quellcode:
.
TObjectList<TGraphicControl>
|
AW: Freigabe Thread
Liste der Anhänge anzeigen (Anzahl: 1)
Denk dir die Sammelliste weg, damit hat es nichts zu tun (habe alles zur Sammelliste gelöscht, gleiches Speicherleck -> siehe Anhang).
Die Liste ist es nicht, es ist wirklich der Thread... |
AW: Freigabe Thread
Zitat:
Nein, siehe mein Beitrag gerade eben. Die Liste kann man ausklammern, selber Fehler
Delphi-Quellcode:
unit Unit1;
interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; type TAnalyseThread = class(TThread) private protected procedure Execute; override; public Status : AnsiString; OnReady : TNotifyEvent; constructor Create(CreateSuspended : Boolean); destructor Destroy; override; end; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); procedure FormDestroy(Sender: TObject); private { Private-Deklarationen } AnalyseThread : TAnalyseThread; procedure OnAnalyseReady(Sender: TObject); public { Public-Deklarationen } end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); begin if Assigned(AnalyseThread) then begin AnalyseThread.Terminate; FreeAndNil(AnalyseThread); end; AnalyseThread := TAnalyseThread.Create(True); if Assigned(AnalyseThread) then begin AnalyseThread.OnReady := OnAnalyseReady; AnalyseThread.Start; end; end; procedure TForm1.OnAnalyseReady(Sender: TObject); begin //Auswertung des Daten... if Assigned(AnalyseThread) then begin AnalyseThread.Terminate; FreeAndNil(AnalyseThread); end; end; procedure TForm1.FormDestroy(Sender: TObject); begin if Assigned(AnalyseThread) then begin AnalyseThread.Terminate; FreeAndNil(AnalyseThread); end; end; { TAnalyseThread } constructor TAnalyseThread.Create(CreateSuspended: Boolean); begin inherited Create(CreateSuspended); FreeOnTerminate := False; end; destructor TAnalyseThread.Destroy; begin inherited Destroy; end; procedure TAnalyseThread.Execute; begin inherited; //Tue irgendwas bis fertig... if Assigned(OnReady) then OnReady(Self); end; end. |
AW: Freigabe Thread
Da steht doch in dem Dialog aus #4, dass der Thread selbst nicht freigegeben wurde. (was in dem Thread ist, ist demnach eh egal)
PS: Strg+C im Dialog und dann Strg+V hier in ein [QUOTE] ... das wird als "Text" kopiert. |
AW: Freigabe Thread
Woran erkennst du das? Bei mir steht, ein unerwarteter Speicherverlust ist aufgetreten.
Ist doch ein Speicherleck oder nicht? --------------------------- Unexpected Memory Leak --------------------------- An unexpected memory leak has occurred. The unexpected small block leaks are: 53 - 60 bytes: TAnalyseThread x 1 |
AW: Freigabe Thread
Delphi-Quellcode:
Ändere OnAnalyseReady so ab und dein Problem ist erledigt. Jedenfalls auf Basis des Codes aus dem Beitrag hier drüber.
procedure TForm1.OnAnalyseReady(Sender: TObject);
begin // Auswertung des Daten... // if Assigned(AnalyseThread) then // begin // AnalyseThread.Terminate; // FreeAndNil(AnalyseThread); // end; end; Hier draus muss man jetzt nicht wieder 5 bis 10 Seiten machen. Einfach in den Editor kopieren, selber testen, Problem gefunden und erledigt. |
AW: Freigabe Thread
Zitat:
Zitat:
OK, sich selbst kann man nicht in sich freigeben. Ja, hier knallt es dann, aber "leider" werden Exceptions innerhalb von Threads nicht angezeigt. Delphi fängt solche Exceptions aber ab, genauso wie in FVL/FMX ist überall ein Try-Except drumrum (nur in VCL/FMX dann noch mit einem ShowException). Aber im Debugger müsstest du diese Exception sehen können :?: |
AW: Freigabe Thread
Ok, könnt ihr mir noch kurz erklären, warum der Thread im OnAnalyseReady nicht freigegeben werden darf, wenn das doch das Event für mich ist, wo er fertig ist
|
AW: Freigabe Thread
achso, wegen der Freigabe in sich selber... Natürlich. Danke.
Nein, eine Exception kommt nicht. Danke! |
AW: Freigabe Thread
Ich habe davon ehrlich gesagt keine Ahnung. Aber ich schätze, dass das Event im selben Kontext steht wie der Thread ansich. Irgendwie so ...
Einfach den Thread ganz abgekoppelt von außerhalb freigeben. |
AW: Freigabe Thread
Thread-Exceptions ausgeben
Delphi-Quellcode:
oder über das Event
type
TAnalyseThread = class(TThread) protected procedure DoTerminate; override; // oder z.B. als MyOnTerminate über OnTerminate end; procedure TAnalyseThread.DoTerminate; begin if Assigned(FatalException) then begin // FatalException ist ausschließlich im OnTerminate/DoTerminate verfügbar var S := Exception(FatalException).Message; // Exception oder Message kopieren/klonen, denn später im Queue ist sie schon weg. TThread.Queue(nil, procedure begin MessageBox(Application.MainFormHandle, PChar(S), 'Thread-Error', MB_OK or MB_ICONERROR); end); end; inherited; end;
Delphi-Quellcode:
oder
constructor TAnalyseThread.Create(CreateSuspended: Boolean);
begin OnTerminate := MyOnTerminate; inherited; end;
Delphi-Quellcode:
Und es ist absichtlich Queue anstatt Synchronize, damit ein Deadlock beim Freigeben verhindert wird.
procedure TAnalyseThread.Execute;
begin try ... // hier dazwischen alles erstellen/freigeben ... nichts im Create/Destroy except on E: Exception do begin var S := E.Message; // Exception oder Message kopieren/klonen, denn später im Queue ist sie schon weg. Queue(procedure begin MessageBox(Application.MainFormHandle, PChar(S), 'Thread-Error', MB_OK or MB_ICONERROR); end); end; end; end; z.B. Thread wartet auf MainThread, aber MainThread wartet im Thread.Free auf das Thread-Ende. PS:
Delphi-Quellcode:
und dann anstatt
type
TAnalyseThread = class(TThread) public OnReady: TNotifyEvent; constructor Create(OnReady: TNotifyEvent=nil); end; constructor TAnalyseThread.Create(OnReady: TNotifyEvent); begin inherited Create(not Assigned(OnReady)); Self.OnReady := OnReady; FreeOnTerminate := False; end; Zitat:
Delphi-Quellcode:
FreeAndNil(AnalyseThread);
AnalyseThread := TAnalyseThread.Create(OnAnalyseReady); |
AW: Freigabe Thread
So eher nicht:
Code:
Erzeugen im Mainthread und im TAnalyseThread-Kontext freigeben ist halt eher ungünstig!
Unit1.TAnalyseThread.Destroy
System.TObject.Free Unit1.TForm1.OnAnalyseReady($2E7DDC0) Unit1.TAnalyseThread.Execute System.Classes.ThreadProc($2E7DDC0) System.ThreadWrapper($2E4A4A0) :7749fa29 KERNEL32.BaseThreadInitThunk + 0x19 :775e76b4 ntdll.RtlGetAppContainerNamedObjectPath + 0xe4 :775e7684 ntdll.RtlGetAppContainerNamedObjectPath + 0xb4 Biete folgende Lösung an:
Delphi-Quellcode:
unit Unit1;
interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; type TAnalyseThread = class(TThread) private protected procedure Execute; override; public constructor Create(CreateSuspended: Boolean); destructor Destroy; override; end; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); procedure FormDestroy(Sender: TObject); private AnalyseThread: TAnalyseThread; procedure OnAnalyseReady(Sender: TObject); end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); begin if Assigned(AnalyseThread) then begin FreeAndNil(AnalyseThread); end; AnalyseThread := TAnalyseThread.Create(True); if Assigned(AnalyseThread) then begin // neu, jetzt noch besser! AnalyseThread.OnTerminate := OnAnalyseReady; AnalyseThread.Start; end; end; procedure TForm1.OnAnalyseReady(Sender: TObject); begin // Auswertung des Daten... if Assigned(AnalyseThread) then begin // Wir sind mit allen durch, aber können hier noch nicht freigeben, weil wir noch zu sehr im Sumpf // des Synchronize drin sind (siehe procedure TThread.DoTerminate;). Darum noch ne Extra-Runde durch // die Windows-Botschaftsbehandlung durch TThread.ForceQueue TThread.ForceQueue(nil, procedure begin FreeAndNil(AnalyseThread); end); end; end; procedure TForm1.FormDestroy(Sender: TObject); begin if Assigned(AnalyseThread) then begin FreeAndNil(AnalyseThread); end; end; { TAnalyseThread } constructor TAnalyseThread.Create(CreateSuspended: Boolean); begin FreeOnTerminate := False; inherited Create(CreateSuspended); TThread.NameThreadForDebugging('TAnalyseThread', Self.ThreadID); end; destructor TAnalyseThread.Destroy; begin inherited Destroy; end; procedure TAnalyseThread.Execute; begin // Tue irgendwas bis fertig... end; end. |
AW: Freigabe Thread
Zitat:
Ich schneide mir ja auch nicht den Ast ab, auf dem ich sitze. Von außen erzeugen und von außen freigeben. |
AW: Freigabe Thread
Ok, habs verstanden. Ich mache es im OnAnalyseReady einfach nicht un gut ist. Danke
|
AW: Freigabe Thread
Zitat:
Wie gesagt, im Programm selber werden unbehandelte Exceptions im Thread von Delphi abgefangen (und dann böswillig nicht ausgegeben). Zitat:
Wann haben die das umgebaut? bin mir fast sicher FatalException wurde früher vor Destroy freigegeben. Somit ist es jetzt aber auch im Destroy noch verfügbar. :firejump: [/EDIT] Denn Exceptions in irgendeinem Thread (auch dem MainThread), welche bis zum System durchrauschen, da beendet dann Windows gleich den kompletten Prozess, drum fängt TThread sowas ab. |
AW: Freigabe Thread
auch im Debugger nicht aber liegt wahrscheinlich daran, dass ich einige Exceptions raus genommen habe (absichtlich) und da ist die bestimmt mit dabei
|
AW: Freigabe Thread
Zitat:
An der Stelle sind wir im MainThread-Kontext und forcieren nochmal das abarbeiten weiterer Messages, indem wir das Freigeben per ForceQueue nochmal auf Reisen schicken. |
AW: Freigabe Thread
Zitat:
Code:
paramdata := TEtwas.Create; // Parameter für den Task. Werden am Ende gelöscht.
TTask.Run( procedure var local : Integer; begin try // To was ... // Uebergib das Ergebnis an das Programm TThread.Synchronize( nil, procedure begin Memo1.Text := 'Bin Fertig'; end ); finally paramdata.Free; end; end ); |
AW: Freigabe Thread
Hallo,
Was ich in deinem Originalcode für kritisch halte ist die Sequenz aus .Terminate; gefolgt von .free; da der Aufruf von Terminate lediglich das Ende des Threads einleitet aber bei der Rückkehr der Thread ggf. noch läuft und du einen laufenden Thread frei gibst. Ich verwende steht's die Sequenz aus. .Terminate; .WaitFor; .free; Auch würde ich davon abraten den Thread direkt im .Execute freizugeben. Was du mit deinem OnReady Ereignis tust. André |
AW: Freigabe Thread
Ich würde in so einem Fall auch TTask.Run nehmen...
es sei den, Ich will nicht den Overhead und erzeuge mir vorab einen Thread. Diese kille ich am Ende des Programms. Der Thread wartet auch einen TEvent und verbraucht keine Taktzyklen, starten aber in wenigen Pico Sekunden. Dafür erzeuge ich ggf. eine Queue in die ich neue Werte schreibe und der Thread startet, wenn das passiert ist. Oder halt nur einen Wert. Also Werte an Thread übergeben, SetEvent aufrufen fertig. Wenn der Thread die Arbeit erledigt hat, legt er sich wieder schlafen... Oder ich nutze FreeOnTerminate und gebe dem Thread ein Notifyer Interface mit, dass dann die Werte halten kann, wenn der Thread schon lange tot ist. Zum Beispiel so:
Delphi-Quellcode:
Natürlich kann ich daraus auch einen INotify<T> und TWorker<T> machen und das dann für alle möglichen Typen verwenden.
program Project13;
{$APPTYPE CONSOLE} uses System.Classes , System.SysUtils , Windows ; Type INotify = Interface ['{27E27AAC-63A6-4289-8B58-BCD4039AAE6C}'] Procedure Ready(Value : Integer); End; TNotify = Class(TInterfacedObject,INotify) strict private Procedure Ready(aValue : Integer); private fValue : Integer; fOnReady : TProc<Integer>; public Constructor Create(aProc : TProc<Integer>); Destructor Destroy;override; end; TWorker = Class(TThread) Protected Procedure Execute;override; private fNotify : INotify; public Constructor Create(aNotify : INotify); End; { TNotify } constructor TNotify.Create(aProc: TProc<Integer>); begin Inherited Create; fOnReady := aProc; end; destructor TNotify.Destroy; begin fOnReady(fValue); inherited; end; procedure TNotify.Ready(aValue: Integer); begin fValue := aValue; end; { TWorker } constructor TWorker.Create(aNotify: INotify); begin fNotify := aNotify; FreeOnTerminate := true; inherited Create(false); end; procedure TWorker.Execute; begin Sleep(5000); fNotify.Ready(42); end; var Worker : TWorker; begin try Worker := TWorker.Create(TNotify.Create(Procedure (aValue : Integer) begin // Still Thread Context Writeln(aValue,' MainThread:',Windows.GetCurrentThreadId() = System.MainThreadID); end)); except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; Readln; end. In diesem Fall, würde ich den Execute Teil, ggf. Überschreiben. Mavarik :coder: |
AW: Freigabe Thread
TTask habe ich ehrlich gesagt noch nie genutzt. Müsste ich mir mal angucken.
Das Beispiel von Mavarik sieht mehr irgendwie zu "viel" aus. Da ist der einfache Thread mit seinem Create/Destroy und Execute für mich übersichtlicher, sicherlich aber Ansichtssache. |
AW: Freigabe Thread
Zitat:
Bleib bei deinem Thread der jetzt funktioniert und gut. Ansonsten sieht das mit dem TTask abgespeckt so aus. Dein Ready-Event packst du am Ende wie bei deinem Thread selber rein
Delphi-Quellcode:
TTask.Run(
procedure begin // Code end); |
AW: Freigabe Thread
Zitat:
Zitat:
zu erhalten, wenn der Thread fertig ist. Normalerweise mache ich das noch aufwendiger! Folgendes Beispiel: In einem Form soll eine Aufgabe im Hintergrund ausgeführt werden... Klar kann man das als "Schön-Wetter-Code" schreiben mit:
Delphi-Quellcode:
1. Was ist, wenn der Sleep eine Abfrage auf einen WebServer ist, der nicht wiederkommt oder ein Timeout hat von 5 Min.
Procedure TForm1.Button1OnClick(Sender : TObject);
begin Button1.Enabled := false; TTask.Run(Procedure begin Sleep(5000); // of was richtiges. TThread.Queue(Nil,Procedure begin Edit1.Text := 'Thread ist fertig und hat: 42 ermittelt'); Button1.Enabled := true; end); end); end; 2. Was ist, wenn das eine lange SQL-Abfrage ist die einfach ewig dauert? Problem - der Button, geht nicht wieder auf Enabled... Toll.. Was aber wenn der User das Fester bereits wieder geschlossen hat? Peng Exeption und die fängt auch erstmal keine ab... Man kann natürlich da noch ein try except drum setzen... OK - Dann das Beispiel mit einem eigenen TThread... Was ist wenn der Thread im Execute etwas aufruft, das nicht wieder kommt und damit steht? Richtig, ein TThread.Destroy signalisiert Terminate aber das wir leider nicht ausgeführt. Wenn also das Thread im FormOnDestroy oder wo auch immer freigegeben werden soll, bleibt das Fenster offen und die Anwendung steht. Ich brauche also wenigstens ein Handle, das OutOfScope geht, wenn ich das Form schließe. Das OutOfScope darf nicht versuchen den Thread frei zu geben, sonst steht die Anwendung wieder. Ich nehme das "nur" als signal, um den OnFinish-Class back mitzuteilen, dass er nicht mehr aufs Form zugreifen darf. Und der Thread? - Naja, der lebt als tote Instance solange, bis die Routine im Execute mal zurück kommt oder das Programm beendet wird. Das kann ich auch 10x versuchen... Die wenigen bytes stören mich nicht. Leider gibt es - außer mit einem OutOfProcess Server keine andere Möglichkeit den Thread zu zerstören. Aber ein OutOfProcess Thread wäre dann hierfür vielleicht doch etwas "zu viel"... Mavarik :coder: |
AW: Freigabe Thread
Zitat:
Während des Queue kann das Fenster dann anschließend nicht mehr verschwinden. (außer man hat ein Application.ProgressMessage, einen Dialog oder Ähnliches darin) Bei Dateioperationen auf eine Netzwerkfreigabe, da kann es auch schonmal ewig hängen. Da kann man entweder Asynchron arbeiten, oder auch von außerhalb via ![]() Auch viele Netwerkkomponenten und auch DB-Komponenten haben eine Funktion um von außerhalb Operationen in einem anderen Thread abzubrechen. Falls es keinen Timeout gibt, oder der nicht immer funktioniert, kann man z.B. beim Schließen des Fensters dennoch einen Abbruch einleiten. Den Thread selbst aber hart abzuschießen ( ![]() |
AW: Freigabe Thread
Zitat:
Weil ein Assigned(Self) geht logischerweise nicht. Zitat:
Zitat:
sein eigenes Memory Management hat und ggf. den Thread killt und den Speicher aufräumt. Mavarik :coder: |
Alle Zeitangaben in WEZ +1. Es ist jetzt 13:26 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz