![]() |
Delphi-Version: XE
mehrere Threads sauber beenden
Guten Morgen alle... :hi:
am Wochenende hab ich mich mal mit Threads intensiv beschäftigt. Zuerst hab ich die OmniThreadLibary ausprobiert. So viele MemoryLeaks (aus der Libary) hab ich in meinem Leben noch nicht gesehen. Danach hab ich TThread versucht. Nach gefühlten hunderten Forenbeiträgen, Hilfeseiten und ausprobieren dessen habe ich das Gefühl die Katze beißt sich in den Schwanz... :zwinker: Gegeben: - Mainunit - UnitA - enthällt KlasseA - UnitB - enthällt KlasseB(TThread) mit IdHTTP Funktion: - Mainunit instanziert Objekt von KlasseA - KlasseA erzeugt TObjectlist für die Threads - KlasseA erzeugt Objekte von KlasseB (je nach Anfrage 1-4) - beim Erzeugen des Threads wird der Thread in die Objectlist gelegt - im OnTerminate des Threads das Objekt aus der Liste entfernt - Threads sollen parallel laufen also nicht warten bis einer fertig ist Solange das Programm beendet wird wenn kein Thread am laufen ist, ist alles in Ordnung. Wird die Anwendung bei laufenden Threads beendet bleiben die Threads stehen :shock: Macht man weiter gibt es MemoryLeaks in den Indys weil die Threads einfach abgewürgt werden. Ok, dachte ich, manuell entfernen... Variante 1: - FreeOnTerminate:= True // Threads sollen sich selbst freigeben wenn beendet
Delphi-Quellcode:
* gibt Zugriffsverletungen... Inzwischen weiß ich, daß WaitFor in Verbindung mit FreeOnTerminate nicht zu gebrauchen ist, da das Handle irgendwo weggeworfen wird und dieser "Bug" schon ein paar Jahre existiert.
destructor KlasseA.Destroy;
begin while not (FThreadList.Count = 0) do begin if Assigned((FThreadList.Items[0] as KlasseB)) then (FThreadList.Items[0] as KlasseB).WaitFor; end; end; Variante 2: - FreeOnTerminate:= False;
Delphi-Quellcode:
* soweit so gut. Die aktuellen Threads laufen fertig und sind freigegeben. Nur hab ich dann Im laufenden Betrieb noch haufenweise Objekte im Speicher, welche nicht mehr in der ObjectList stehen, da die Threads sich im OnTerminate austragen.
destructor KlasseA.Destroy;
begin while not (FThreadList.Count = 0) do begin if Assigned((FThreadList.Items[0] as KlasseB)) then begin (FThreadList.Items[0] as KlasseB).WaitFor; (FThreadList.Items[0] as KlasseB).Free; end; end; end; Wo gebe ich quasi im laufenden Betrieb die Objekte frei ? (nach OnTerminate). Die Beiträge in den Foren laufen alle im Prinzip auf folgendes hinaus:
Delphi-Quellcode:
* das funktioniert auch, nur laufen die Threads nicht parallel sondern nacheinander. (logisch)
Thread.Create;
/// Thread.WaitFor; ThreadFree; Bitte bringt etwas Licht ins Dunkel... Danke :hi: |
AW: mehrere Threads sauber beenden
Guten Morgen,
wenn Du die Threads weiter mit freeOnTerminate = true startest und in der Form im onCloseQuery abfragst ob noch Threads laufen. ObjectList.count > 0 und die noch laufenden Threads dann terminierst (oder wartest bis sie abgearbeitet sind) sollte es doch "eigentlich" zu keinen Problemen kommen. Grüße Klaus |
AW: mehrere Threads sauber beenden
Zitat:
...aber Danke für deine Hilfe. :P Nachtrag: Nur mit WaitFor bringt man den entsprechenden Thread zum weiterlaufen. Funktioniert aber nur bei FreeOnTerminate:= False. Und dann stehen wir wieder am Anfang...wo gebe ich die Threads frei ? :zwinker: |
AW: mehrere Threads sauber beenden
Hallo,
mal ein kleines Konstrukt:
Delphi-Quellcode:
Grüße
constructor TTestThread.create;
begin inherited create(false); freeOnTerminate := true; end; procedure TTestThread.execute; begin while not terminated do begin sleep(100); end; end; procedure TForm1.Button1Click(Sender: TObject); begin ThreadList.Add(TTestThread.Create) end; procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); begin showMessage(intToStr(threadList.count)); threadList.free; end; procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean); var i : Byte; begin canClose := false; while ThreadList.Count > 0 do begin for i:= ThreadList.count -1 downto 0 do begin (ThreadList[i] as TTestThread).terminate; while not (ThreadList[i] as TTestThread).Terminated do begin sleep(200); end; ThreadList.Delete(i); end; end; canClose := true; end; procedure TForm1.FormCreate(Sender: TObject); begin ThreadList := TObjectList.Create(false); end; Klaus |
AW: mehrere Threads sauber beenden
Danke für deine Mühe...
Delphi-Quellcode:
aus dieser Schleife kommst du nicht mehr raus, da die Threads einfach stehen. Da kannst du Terminate setzen wie du willst. Terminated wird nie True, weil die Threads nicht arbeiten. Sprich Threadlist.Count bleibt immer das gleiche. Würden die Threads ganz normal weiterlaufen würde dein Konstrukt funktionieren.
for i:= ThreadList.count -1 downto 0 do
begin (ThreadList[i] as TTestThread).terminate; while not (ThreadList[i] as TTestThread).Terminated do begin sleep(200); end; ThreadList.Delete(i); end; |
AW: mehrere Threads sauber beenden
Unter Delphi 2007 funktioniert das Beispiel einwandfrei.
|
AW: mehrere Threads sauber beenden
Hallo,
schon merkwürdig. Wenn ich im execute des Threads ein Sleep von 20 einsetze funktioniert es so wie Du beschrieben hast. Wenn ich aber ein Sleep von 100 einsetze terminiert der Thread und ich bekomme am Ende die Messagebox angezeigt. Grüße Klaus |
AW: mehrere Threads sauber beenden
ok... diese Variante hatte ich wie gesgt schon. Allerdings nicht mit Sleep.
Ich geh dann mal zum probieren....bis gleich. Soooo....
Delphi-Quellcode:
also Terminated auskommentiert.
destructor Klasse1.Destroy; // Kommt auf das gleiche raus wie CloseQuery
var I: Integer; begin while not (FThreadList.Count = 0) do begin if Assigned((FThreadList.Items[0] as TXWebLoader)) then begin while FThreadList.Count > 0 do begin for I := FThreadList.Count - 1 to 0 do begin (FThreadList.Items[0] as TXWebLoader).Terminate; // in XE ist Terminated als protected deklariert ! Geht schon mal nicht ! while not (FThreadList.Items[0] as TXWebLoader).Terminated do begin Sleep(200); end; end; end; FThreadList.Delete(I); end; end;
Delphi-Quellcode:
bei beiden Varianten im Execute Sleep(100) eingefügt...
destructor TXWeb.Destroy;
var I: Integer; begin while not (FThreadList.Count = 0) do begin if Assigned((FThreadList.Items[0] as TXWebLoader)) then begin while FThreadList.Count > 0 do begin for I := FThreadList.Count - 1 to 0 do begin (FThreadList.Items[0] as TXWebLoader).Terminate; Sleep(200); end; end; FThreadList.Delete(I); end; end; Ergebnis: Die Threads stehen wie eine eins und das Programm ist in einer Endlosschleife. |
AW: mehrere Threads sauber beenden
.. kannst Du mal (so grob) Deine Thread.execute Methode
hier einstellen. Grüße Klaus |
AW: mehrere Threads sauber beenden
Zitat:
Damit hälst du doch nur alle Threads an und das Programm steht für 200 Millisekunden Du solltest allen Threads die möglichkeit geben ihre Aktionen zu beenden. Versuchs mal damit
Delphi-Quellcode:
gruss
procedure WinProcessMessages;
// Allow Windows to process other system messages var ProcMsg: TMsg; begin while PeekMessage(ProcMsg, 0, 0, 0, PM_REMOVE) do begin if (ProcMsg.message = WM_QUIT) then Exit; TranslateMessage(ProcMsg); DispatchMessage(ProcMsg); end; end; |
AW: mehrere Threads sauber beenden
Die Threads stehen sowieso und tun nix... Die Klasse1 ist nicht der Thread. Klasse1 erzeugt die Threads. Im Destroy der Klasse1 sollen die Threads aufgeräumt werden. Das Sleep(200) sollte imho dazu da sein, daß´die Threads Zeit haben zu reagieren. Aber die bewegen sich ja nicht.
Laut Status der Threads in der Objektliste: Suspended = False sollten sie das aber nicht. die ThreadUnit Komplett(entspricht Klasse2 des Übersichtsbeispieles):
Delphi-Quellcode:
unit XWebLoader;
interface uses Classes, SysUtils, SyncObjs, IdHTTP, IdComponent, XWebDataTypes; type TOnFinishLoadEvent = procedure(Sender: TObject; LoaderMessage: TXWebLoaderMessage) of object; TOnErrorLoadEvent = procedure(Sender: TObject; LoaderMessage: TXWebLoaderMessage) of object; TOnRemoveEvent = procedure(Sender: TObject) of object; TXWebLoader = class(TThread) strict private FCS: TCriticalSection; FHTTP: TIdHTTP; FXWebLink: string; FParameter: string; FCookie: string; FDeviceName: string; FMessageID: Integer; FDigit: Integer; FGoLoading: Boolean; FOnFinish: TOnFinishLoadEvent; FOnError: TOnErrorLoadEvent; FOnRemove: TOnRemoveEvent; FMsg: TXWebLoaderMessage; procedure SyncOnFinish; procedure SyncOnError; procedure CheckAbort(Sender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64); protected procedure Execute; override; procedure Remove(Sender: TObject); public constructor Create(Suspended: Boolean); destructor Destroy; override; procedure GetData(const XWebLink,Parameter,Cookie,DeviceName :string; MessageID,Digit: Integer); property OnFinish: TOnFinishLoadEvent read FOnFinish write FOnFinish; property OnError: TOnErrorLoadEvent read FOnError write FOnError; property OnRemove: TOnRemoveEvent read FOnRemove write FOnRemove; end; implementation { TXWebLoader } procedure TXWebLoader.CheckAbort(Sender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64); // auch ein Versuch den Socket Error der Indys beim Programm Beenden zu kompensieren begin // war ein Tipp in einem Beitrag. Hier kommt das Programm gar nicht an. Die Threads werden if Terminated then // einfach abgewürgt FHTTP.Disconnect; end; constructor TXWebLoader.Create(Suspended: Boolean); begin inherited Create(Suspended); FCS:= TCriticalSection.Create; FHTTP:= TIdHTTP.Create; FHTTP.OnWork:= CheckAbort; FMsg:= TXWebLoaderMessage.Create; Self.OnTerminate:= Remove; end; destructor TXWebLoader.Destroy; begin FHTTP.Free; FMsg.Free; FreeAndNil(FCS); inherited; end; procedure TXWebLoader.Execute; var XWebParameter: TStringStream; ResponseStream: TStringStream; sl: TStringList; begin inherited; if not Terminated then begin sl:= TStringList.Create; try XWebParameter:= TStringStream.Create(FParameter); try ResponseStream:= TStringStream.Create; try try FHTTP.Request.CustomHeaders.Add(FCookie); if FParameter = '' then FHTTP.Get(FXWebLink,ResponseStream) else FHTTP.Post(FXWebLink,XWebParameter,ResponseStream); ResponseStream.Position:= 0; sl.LoadFromStream(ResponseStream,TEncoding.UTF8); FMsg.Data:= sl.Text; Synchronize(SyncOnFinish); except on e: Exception do begin // evt. noch weitere Informationen in Stringlist oder Klasse dafür ? FMsg.Data:= e.Message; Synchronize(SyncOnError); end; end; finally ResponseStream.Free; end; finally XWebParameter.Free; end; finally sl.Free; end; Sleep(100); // extra eingefügt end; end; procedure TXWebLoader.GetData(const XWebLink, Parameter, Cookie, DeviceName :string; MessageID,Digit: Integer); begin FCS.Enter; try FXWebLink:= XWebLink; FParameter:= Parameter; FCookie:= Cookie; FDeviceName:= DeviceName; FMessageID:= MessageID; FDigit:= Digit; FMsg.ID:= FMessageID; FMsg.DeviceName:= FDeviceName; FMsg.Digit:= FDigit; Self.Start; finally FCS.Leave; end; end; procedure TXWebLoader.SyncOnError; begin FCS.Enter; try if Assigned(FOnError) then FOnError(Self,FMsg); finally FCS.Leave; end; end; procedure TXWebLoader.SyncOnFinish; begin FCS.Enter; try if Assigned(FOnFinish) then FOnFinish(Self,FMsg); finally FCS.Leave; end; end; procedure TXWebLoader.Remove(Sender: TObject); begin FCS.Enter; try if Assigned(FOnRemove) then FOnRemove(Self); finally FCS.Leave; end; end; end. |
AW: mehrere Threads sauber beenden
Zitat:
Ich habe da andere Erfahrungen gesammelt. Solange nicht alle Processe korrekt beendet wurden wirst du immer in einer Endlosschleife enden. gruss |
AW: mehrere Threads sauber beenden
.. mal ein Schuss ins Blaue.
Wie verhält sich das ganze denn, wenn Du die synchronize Aufrufe mal auskommentierst. Die Form soll geschlossen werden - dann kommt noch ein synch Aufruf um etwas anzuzeigen?? Nachtrag: Was bezweckst Du mit dem inherited Aufruf zu Beginn der execute Methode? Grüße Klaus |
AW: mehrere Threads sauber beenden
Zitat:
Neuigkeiten: - vieleicht sind wir alle auf dem Holzweg und es funktioniert doch...:zwinker: Wir suchen grade, weil ich behauptet habe, daß die Threads stehen und nix tun. Diese Aussage ist in der Tatsache begründet, daß die IDE (Debugger) immer noch die Threads als vorhanden anzeigt aber keine Aktivität zu verzeichnen ist. Normalerweise sollte die IDE nach Beendigung des ganzen ja in den Entwurfsmodus zurückkehren. Es passiert folgendes: - wenn ich das Programm ohne laufende Threads beende kommt die IDE in den Entwurfsmodus zurück - Wenn ich das Programm mit laufenden Threads beende sieht es aus als ob eine Endlosschleife vorliegt. - Beende ich den Task im Taskmanager kommt die IDE in dem Entwurfsmodus zurück. - Starte ich nur die EXE wird der Task beendet. ... und das alles ohne Memory Leaks. Kann das jemand erklären ? |
AW: mehrere Threads sauber beenden
Delphi-Quellcode:
innerhalb einer CriticalSection ???
Self.Start;
|
AW: mehrere Threads sauber beenden
macht keinen Unterschied...
Kommando zurück: Beitrag 14 ist Quatsch. - in der Schleife stand to statt downto - Das FThreadList.Delete[1] stand außerhalb der Schleife. Fazit: Das war wirklich eine Endlosschleife. mal ein Auszug aus dem Ereignisprotokoll:
Delphi-Quellcode:
Thread-Start: Thread-ID: 292. Prozess XWebDemo.exe (6096) // normaler Threadstart
Thread-Start: Thread-ID: 5992. Prozess XWebDemo.exe (6096)// normaler Threadstart Thread-Start: Thread-ID: 2520. Prozess XWebDemo.exe (6096)// normaler Threadstart Thread-Ende: Thread-ID: 292. Prozess XWebDemo.exe (6096)// normales Ende Thread-Ende: Thread-ID: 5992. Prozess XWebDemo.exe (6096)// normales Ende Thread-Ende: Thread-ID: 2520. Prozess XWebDemo.exe (6096)// normales Ende Thread-Start: Thread-ID: 5596. Prozess XWebDemo.exe (6096)// normaler Start Thread-Start: Thread-ID: 5072. Prozess XWebDemo.exe (6096)// normaler Start Thread-Start: Thread-ID: 5056. Prozess XWebDemo.exe (6096)// normaler Start --> Beenden geklickt Quelltexthaltepunkt bei $005DF477: D:\Projekte\Delphi Unicode\XWeb500\DemoThreadVersion_1\XWebBase.pas Zeile 115. Prozess XWebDemo.exe (6096) // while not (FThreadList.Count = 0) do // siehe TXWeb.Destroy // Schleife wird durchlaufen bis Liste leer... // die Threads stehen, sonst wären sie schon fertig Modul entladen: Security.dll. Prozess XWebDemo.exe (6096) Modul entladen: WSHTCPIP.dll. Prozess XWebDemo.exe (6096) Erste Gelegenheit für Exception bei $7C812AFB. Exception-Klasse EIdSocketError mit Meldung 'Socket Error # 10093 // Indy Meldung '. Prozess XWebDemo.exe (6096) Erste Gelegenheit für Exception bei $7C812AFB. Exception-Klasse EIdSocketError mit Meldung 'Socket Error # 10093 // Indy Meldung '. Prozess XWebDemo.exe (6096) Modul entladen: HNetCfg.dll. Prozess XWebDemo.exe (6096) Thread-Ende: Thread-ID: 5072. Prozess XWebDemo.exe (6096) // erst hier werden die Threads beendet Thread-Ende: Thread-ID: 5596. Prozess XWebDemo.exe (6096) Thread-Ende: Thread-ID: 5056. Prozess XWebDemo.exe (6096) |
AW: mehrere Threads sauber beenden
Kurze Rückmeldung... :hi:
mit Unterstützung von DeddyH sind wir auf folgendes Lösungsprinzip gekommen. 1. eine Liste für die Threads (hier als generische Liste unter XE)
Delphi-Quellcode:
TMyCustomThreadList = TList<TLoader>; // TLoader = class TThread
TMyThreadList = class(TCustomThreadList) strict private FListHandle: HWND; FDestroying: Boolean; procedure TreadFinished(var Msg: TMessage); message PM_Finish_Thread; // Methode die die Message empfängt public constructor Create; destructor Destroy; override; procedure Clear; procedure Add(aThread: TLoader); procedure Remove(aThread: TLoader); end;
Delphi-Quellcode:
constructor TMyThreadList.Create;
begin inherited; // Fensterhandle erzeugen damit auch Messages enmpfangen werden können FListHandle := AllocateHWnd(TreadFinished); end;
Delphi-Quellcode:
destructor TMyThreadList.Destroy;
begin Clear; DeAllocateHwnd(FListHandle); inherited; end;
Delphi-Quellcode:
procedure TMyThreadList.Remove(aThread: TLoader);
begin aThread.Terminate; aThread.WaitFor; aThread.Free; inherited Remove(aThread); end;
Delphi-Quellcode:
procedure TMyThreadList.Clear;
begin FDestroying:= True; while Count > 0 do Remove(Items[0]); end;
Delphi-Quellcode:
2. Thread erzeugen und starten
procedure TMyThreadList.Add(aThread: TLoader);
begin inherited Add(aThread); aThread.ListHandle := FListHandle; end;
Delphi-Quellcode:
3. Thread versendet User Message wenn fertig
Loader:= TLoader.Create;
// Zuweisung der Methode die nach der Arbeit des Threads ausgeführt wird (Methoden anpassen) Loader.OnFinish:= FinishLoad; FThreads.Add(Loader); // FThreads private Property von TMyThreadList Loader.Start; // Start gibts erst ab 2010 ? Ansonsten Resume;
Delphi-Quellcode:
4. Message kommt an
const
PM_Finish_Thread = WM_USER + 1; . . // Methode die der TThread Methode OnTerminate zugewiesen ist procedure TLoader.OnThreadTerminated(Sender: TObject); begin PostMessage(FListHandle, PM_Finish_Thread, wParam(Self), 0); end;
Delphi-Quellcode:
Fazit:
procedure TXWebThreadList.TreadFinished(var Msg: TMessage);
begin //FDestroying wird im destructor gesetzt, damit nicht 2 Mal entfernt wird. Wenn die Liste beim Beenden der //Anwendung die Liste leer macht würde die Message ja auch ankommen. if (not FDestroying) and (Msg.Msg = PM_Finish_Thread) then Remove(TXWebLoader(Msg.wParam)); // Thread aus Liste entfernen und freigeben end; Der Unterschied besteht eigentlich nur darin, daß: 1. Der Thread über eine Message mitteilt daß er fertig ist. Nicht Ereignis. Ereignise werden sequentiell abgearbeitet und der Thread konnte sich nicht beenden solange die Ereignisse nicht abgearbeitet sind. Da war die Freigabe das Problem. Da kam die "Endlosschleife" her. 2. die Liste selbst die Threads entfernt und freigibt 3. dadurch daß jetzt WaitFor ordentlich funktioniert weil die Threads weiterlaufen (wegen Message) werden die Threads sauber beendet und freigegeben. Hoffe, daß das Ganze anderen viel Nerven erspart... :hi: |
AW: mehrere Threads sauber beenden
Hallo,
super ansatz mit messages zu arbeiten der funktioniert !! nur ein kleiner tipfehler ist hier : Postmessage(flisthandle,PM_FINISHED_THREAD,wparam( self),0); sollte eigentlich heissen Postmessage(flisthandle,PM_FINISHED_THREAD,wparam( sender),0); sonst killt man den mainprocess |
Alle Zeitangaben in WEZ +1. Es ist jetzt 09:45 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