![]() |
Delphi-Version: XE2
Arbeiten mit TThreadList
Ich schreibe gerade an einem Programm, in dem eine vom Benutzer festgelegte Anzahl an Threads starten sollen. Da ich bei Programmende alles schön wieder aufräumen will, muß ich ja dafür sorgen, dass alle Threads brav beendet sind.
Kann ich dafür die TThreadList verwenden? Funktioniert das ähnlich wie eine TObjectList? Also TObject->TObjectList und TThread->TThreadList? Gibt's irgendwo ein schickes Tutorial, dass man auch versteht, wenn bisher wenig mit TThreads gearbeitet hat? Oder gibt's eine bessere oder einfachere Möglichkeit, die TThreads in einer Liste zu verwalten, und diese beim Beenden abzuarbeiten. |
AW: Arbeiten mit TThreadList
Einfach in eine TObjectList packen, aber beim Aufräumen die Threads beenden, auf Fertigstellung warten und dann wegwerfen.
Eine ![]() ![]() Achtung: Das Codebeispiel bitte nur unter dem Aspekt TThtreadList betrachten, ansonsten ist das ein absolutes NoGo-Beispiel |
AW: Arbeiten mit TThreadList
Ah, cool. Danke für deine schnelle Hilfe.
|
AW: Arbeiten mit TThreadList
Was willst Du denn mit der Begrenzung erreichen? Etwa eine Steuerung der Auslastung?
|
AW: Arbeiten mit TThreadList
Zitat:
Ich will nur wissen, wie viele Thread laufen, damit ich die bei Programmende sauber beenden kann. |
AW: Arbeiten mit TThreadList
Zitat:
|
AW: Arbeiten mit TThreadList
Zitat:
|
AW: Arbeiten mit TThreadList
Zitat:
Grüße Klaus |
AW: Arbeiten mit TThreadList
Zitat:
Dem zur Folge für meine Zwecke völlig okay. Ich hab's grad mal mit 20 Threads auf einem 1 Prozessorsystem getestet und hatte 0,1% Auslastung. Laufen wird es nachher auf einem 8-Core-System. [OT on] Hey, danke für eurer Interesse. Aber mir ging es tatsächlich um die Frage, die schon im ersten Thread stand. Nicht darum, ob und warum man die Threadanzahl begrenzen sollte. Solche Diskussionen ziehen diesen Beitrag nur unnötig in die Länge und nützen am Ende keinem. Außerdem wären dafür noch ganz andere Informationen über den Sinn und Zweck des Programm und der Threads notwendig. Jetzt aber nicht gleich beleidigt sein, okay :-D Daniel hatte es für mich mit seiner Antwort schon wie immer sehr treffend auf dem Punkt gebracht. :thumb::thumb: [OT off] |
AW: Arbeiten mit TThreadList
Zitat:
|
AW: Arbeiten mit TThreadList
Zitat:
Meinte natürlich dich :-D:-D Verd*mt, hab euch glatt verwechselt. Wie peinlich :lol::lol: Also, natürlich hast du es auf den Punkt gebracht. Großes Sorry |
AW: Arbeiten mit TThreadList
Hi, ich muß diese Thema noch mal aufwärmen, da ich da noch ein paar Probleme habe.
Also ich erzeuge mir eine TThreadList, die ich beim Beenden des MainThread entfernen möchte.
Delphi-Quellcode:
Sicherlich nicht korrekt, denn im FormDestroy kommt es beim RS232ThreadList.Free zu einer Exception, da die Thread noch nicht beendet sind.
procedure Tfrm_main.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var I: Integer; r: Cardinal; begin for I := RS232ThreadList.Count-1 downto 0 do begin TComThread(RS232ThreadList[i]).StopListen; TComThread(RS232ThreadList[i]).Terminate; r:=WaitForSingleObject(TComThread(RS232ThreadList[i]).Handle, 1000); //ShowMessage(IntToStr(r)); end; end; procedure Tfrm_main.FormCreate(Sender: TObject); begin RS232ThreadList:=TObjectList.Create; end; procedure Tfrm_main.FormDestroy(Sender: TObject); begin RS232ThreadList.Free; end; Bzw. genau genommen, lande ich in System.Classes im Destructor TThread.Destroy beim CloseHandle; Meine Annahme, die Threads in der ThreadList sind noch nicht beendet. Wie mache ich es richtig? Ich muß ja im OnCanClose auf das Beenden der Threads warten. |
AW: Arbeiten mit TThreadList
Zeig mal
a) wie du die Threads erzeugst (FreeOnTerminate?) b) wie du die Liste erzeugst (OnwsObjects?) |
AW: Arbeiten mit TThreadList
Wieso wartest Du nur maximal eine Sekunde auf das Handle? Warte doch lieber, bis das Handle die Fahne oben hat, auch wenn's dauert...
Und falls es sich dabei um Threads handelt, würde ich mit 'WaitFor' noch warten, bis das Teil auch wirklich beendet ist und es dann ggf. explizit per Free freigeben (außer, das macht die ThreadListe selbst) |
AW: Arbeiten mit TThreadList
Mit OwnObject:=False funktionierts. FreeOnTerminate ist True.
Jetzt werden die Objecte (Thread) also freigegen, wenn die Liste freigegeben werden. Aber eigentlich müßte es ja auch anders gehen. Ist denn das mit Waitforsingleobject so korrekt? Was ist eigentlich der Rückgabewert von Waitforsingleobject? In meinem Fall habe ich immer 0 zurück bekommen. Das mit 1000 Milisekunden ist erst mal zum Testen gewesen. Mit WaitFor on OnCanClose bekomme ich immer die EThread-Exception "Das Handle ist ungültig(6)". |
AW: Arbeiten mit TThreadList
Das ist die
Delphi-Quellcode:
aus
TThreadList
Delphi-Quellcode:
oder
System.Classes
Delphi-Quellcode:
, oder? Ich kann der nichts abgewinnen: Da fehlen elementarste Dinge wie ein
System.Generics.Collections
Delphi-Quellcode:
und alles. Bist du sicher, dass du die brauchst? Ich hätte gedacht, dass du die RS232-Threads alle im Hauptthread erstellst und an die Liste dranhängst und ebenso dort auch die Liste wieder zumachen willst?
GetEnumerator()
Ich nehme da einfach eine ganz "normale" Objektliste (
Delphi-Quellcode:
)- Soweit die
TObjectList
Delphi-Quellcode:
hat, ist die Freigabe der Liste mitsamt aller enthaltenen Threads so einfach wie ein
OwnsObjects = True
Delphi-Quellcode:
Bsp:
meineListe.Free();
Delphi-Quellcode:
program Project4;
{$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.SyncObjs, System.Classes, System.Generics.Collections; const numThreads: Integer = 5; var threadList: TObjectList<TThread>; threadNum: Integer; threadIterator: TThread; consoleCs: TSynchroObject; //{$DEFINE OWNSOBJECTS} procedure writeConsole(const line: String); begin consoleCs.Acquire(); try WriteLn(line); finally consoleCs.Release(); end; end; function constructRunningThread(const threadNum: Integer): TThread; begin Result := TThread.CreateAnonymousThread( procedure begin writeConsole('Thread ' + threadNum.ToString() + ' startet...'); sleep(1000 + Random(4000)); writeConsole('Thread ' + threadNum.ToString() + ' endet...'); end ); Result.FreeOnTerminate := {$IFDEF OWNSOBJECTS}False{$ELSE}True{$ENDIF}; Result.Start(); end; begin try consoleCs := TCriticalSection.Create(); writeConsole('Erstelle und fülle Liste...'); threadList := TObjectList<TThread>.Create( {$IFDEF OWNSOBJECTS}True{$ELSE}False{$ENDIF} ); for threadNum := 0 to Pred(numThreads) do threadList.Add(constructRunningThread(threadNum)); writeConsole('Baue Liste ab...'); threadList.Destroy(); writeConsole('Liste abgebaut'); writeConsole('<Taste drücken>'); except on E: Exception do WriteLn(E.ClassName, ': ', E.Message); end; ReadLn; end. // Roter Kasten: Entweder ein TThread hat FreeOnTerminate = True und du fasst ihn nach dem Starten nicht mehr an(!) oder er hat es auf False und du gibst die TThread-Instanz selbst so frei wie du möchtest. Ich habe das mal versucht in das Beispiel zu packen: Du kannst das
Delphi-Quellcode:
einmal auskommentieren und dir den Unterschied anschauen :-)
{$DEFINE OWNSOBJECTS}
|
AW: Arbeiten mit TThreadList
Zitat:
Besser FreeOnTerminate auf false und dafür OwnsObjects auf true. Dann bleiben die Instanzen der Threads auch nach dem Beenden gültig und werden erst mit der Freigabe der Liste auch freigegeben. |
AW: Arbeiten mit TThreadList
Danke, FreeOnTerminate hab ich auf False gesetzt.
@Günther: Das werd ich mir auch noch mal genau anschauen. Aber das mit WaitForSingleObject hab ich noch nicht verstanden. Rückgabewert hab ich in OH gefunden. Aber mit dem Satz "The state of the specified object is signaled" ist mir noch nicht ganz klar. Heißt das jetzt, WaitForSingleObject wartet auf irgendeine Reaktion vom Object? |
AW: Arbeiten mit TThreadList
Zitat:
Danke für einen Hinweis, Christoph |
AW: Arbeiten mit TThreadList
Zitat:
|
AW: Arbeiten mit TThreadList
Zitat:
Vielleicht kam das erst mit XE? Aber ansonsten wäre es kein Unterschied, stattdessen mit (weitaus mehr Tippaufwand) sich auf klassischem Wege wieder eine Klasse
Delphi-Quellcode:
zu definieren,
TMyThread = class(TThread)
Delphi-Quellcode:
zu implementieren und den letztendlich zurückzugeben.
Execute()
|
AW: Arbeiten mit TThreadList
Ich verstehe gar nicht, was dieses Gehampel mit den Threads soll.
Delphi-Quellcode:
(ja, OwnsObjects auf True) und wenn diese Threads aus dem Speicher sollen, dann ein ganz lapidares freigeben der Liste.
TObjectList
Ein Thread ruft im Destroy ganz von alleine das ![]()
Delphi-Quellcode:
.
TThread.WaitFor
Fazit: Die Thread-Klasse sauber aufbauen, dann klappt das wie von selber |
AW: Arbeiten mit TThreadList
Zitat:
|
AW: Arbeiten mit TThreadList
Apropos WaitFor. Darf ich mich mal kurz einklinken? Kann man das auch in den destructor schreiben oder ist es da schon zu spät?
Delphi-Quellcode:
constructor TFindSnapPointsThread.Create(List: TGraphicList);
begin inherited Create(true); FreeOnTerminate := true; FFindSnapPoints := TFindSnapPoints.Create; FList := List end; destructor TFindSnapPointsThread.Destroy; begin WaitFor; FFindSnapPoints.Free; inherited Destroy; end; procedure TFindSnapPointsThread.Terminate; begin FFindSnapPoints.Cancel := true; inherited Terminate; end; |
AW: Arbeiten mit TThreadList
Es sollte reichen, wenn du die Instanzen nach dem
Delphi-Quellcode:
freigibst.
inherited
Delphi-Quellcode:
Um hier aber etwas wirklich konkretes zu sagen, musst du dir dringend einmal anschauen, was in
destructor TFindSnapPointsThread.Destroy;
begin inherited; FFindSnapPoints.Free; end;
Delphi-Quellcode:
so passiert.
TThread.Destroy
In den neueren Delphi-Versionen wird dort eben genau das alles gemacht (
Delphi-Quellcode:
,
Terminate
Delphi-Quellcode:
,...).
WaitFor
Zitat:
Delphi-Quellcode:
zu klären sein, ob das wirklich nötig ist, oder ob es einfach reicht, den echten Destructor abzuwarten und dann erst alles einzureißen (s.o.)
TThread.Destroy
|
AW: Arbeiten mit TThreadList
Hier mal ein Beispiel-Thread, der sich nach außen hin "harmlos" verhält.
Delphi-Quellcode:
TMyForm = class( TForm )
procedure Button1Click( Sender:TObject ); private FMyThread : TMyThread; public procedure AfterConstruction; override; procedure BeforeDestruction; override; end; procedure TMyThread.Button1Click( Sender:TObject ); var LFoo : TFoo; begin // Feed the Thread ... LFoo := TFoo.Create; try FMyThread.WorkOnItem( LFoo ); LFoo := nil; // Wenn die Instanz vom Thread übernommen wurde, dann hier auf nil setzen finally LFoo.Free; // stellt bei einer Exception sicher, dass die Instanz freigegeben wird end; end; procedure TMyForm.AfterConstruction; begin inherited; FMyThread :=TMyThread.Create; end; procedure TMyForm.BeforeDestruction; begin FreeAndNil( FMyThread ); // erst wenn die Thread-Instanz wirklich freigegeben werden konnte, dann geht es hier weiter inherited; end;
Delphi-Quellcode:
UPDATE
unit Unit1;
interface // In älteren Delphi-Versionen hat die Thread-Klasse noch keine TerminatedSet-Methode. // Dann bitte das nachfolgende {$DEFINE USE_TERMINATEDSET} ausschalten {$DEFINE USE_TERMINATEDSET } uses Classes, SyncObjs, Contnrs; type TMyThread = class( TThread ) private FCS : TCriticalSection; FEvent : TEvent; FToDoList : TObjectList; procedure DoWorkOnItem( Item : TObject ); function GetItem : TObject; protected procedure Execute; override; {$IFDEF USE_TERMINATEDSET} procedure TerminatedSet; override; {$ENDIF} public constructor Create; destructor Destroy; override; // Übergabe eines WorkItems an den Thread. // Der Thread übernimmt die Kontrolle über die Item-Instanz // und gibt diese bei Bedarf auch wieder frei // - Nach dem Abarbeiten // - Beim Beenden, wenn noch Items in der Liste enthalten sind procedure WorkOnItem( Item : TObject ); end; implementation { TMyThread } constructor TMyThread.Create; begin inherited Create( False ); // <-- NEIN, der Thread soll nie, nicht, niemals schlafen FCS := TCriticalSection.Create; FEvent := TEvent.Create( nil, False, False, '' ); FToDoList := TObjectList.Create( True ); end; destructor TMyThread.Destroy; begin {$IFDEF USE_TERMINATEDSET} // hier einfach nichts machen ... abwarten und Tee trinken {$ELSE} Terminate; FEvent.SetEvent; {$ENDIF} inherited; // jetzt alle Instanzen freigeben FToDoList.Free; FEvent.Free; FCS.Free; end; procedure TMyThread.DoWorkOnItem( Item : TObject ); begin // Hier irgendetwas mit dem Item machen end; procedure TMyThread.Execute; var LItem : TObject; begin inherited; // die übliche Schleife ... while not Terminated do begin // Warten, bis es etwas zu arbeiten gibt FEvent.WaitFor; // Wenn der Event gefeuert wurde, prüfen wir mal ob ... if not Terminated then begin // Item aus der ToDoListe holen LItem := GetItem; try // Mit dem Item arbeiten DoWorkOnItem( LItem ); finally // Item-Instanz freigeben LItem.Free; end; end; end; end; function TMyThread.GetItem : TObject; begin FCS.Enter; try // Item aus der ToDoListe entnehmen Result := FToDoList.Extract( FToDoList.First ); // Wenn dort nocht Items enthalten sind, dann setzen wir den Event auch wieder if FToDoList.Count > 0 then FEvent.SetEvent; finally FCS.Leave; end; end; {$IFDEF USE_TERMINATEDSET} procedure TMyThread.TerminatedSet; begin inherited; // Wenn Terminted, dann braucht hier keiner mehr warten FEvent.SetEvent; end; {$ENDIF} procedure TMyThread.WorkOnItem( Item : TObject ); begin FCS.Enter; try // Item in die ToDoListe einfügen FToDoList.Add( Item ); // und per Event den Thread aufwecken FEvent.SetEvent; finally FCS.Enter; end; end; end. ![]() |
AW: Arbeiten mit TThreadList
Ok. Vielen Dank für dein Beispiel. Die BeforeDestruction werd' ich einbauen.
Delphi-Quellcode:
destructor TD2007Thread.Destroy;
begin if (FThreadID <> 0) and not FFinished then begin Terminate; if FCreateSuspended then Resume; WaitFor; end; RemoveQueuedEvents(Self, nil); {$IFDEF MSWINDOWS} if FHandle <> 0 then CloseHandle(FHandle); {$ENDIF} {$IFDEF LINUX} // This final check is to ensure that even if the thread was never waited on // its resources will be freed. if FThreadID <> 0 then pthread_detach(FThreadID); sem_destroy(FCreateSuspendedSem); {$ENDIF} inherited Destroy; FFatalException.Free; RemoveThread; end; |
AW: Arbeiten mit TThreadList
Ein Problem sehe ich hier im Destructor
Delphi-Quellcode:
Das Problem kann hier auftauchen:
destructor TD2007Thread.Destroy;
begin if (FThreadID <> 0) and not FFinished then begin Terminate; if FCreateSuspended then Resume; WaitFor; // Diese Stelle ist problematisch end;
Delphi-Quellcode:
Denn nun wird zwar im Destructor korrekterweise das
var
LThread : TThread; begin LThread.Create( True ); LThread.Free; end; ![]() ![]() In neueren Delphi-Versionen wird genau nach dem
Delphi-Quellcode:
noch in Kombination mit
Resume
![]()
Delphi-Quellcode:
if FCreateSuspended or FSuspended then
Resume; while not FStarted do Yield; WaitFor; |
Alle Zeitangaben in WEZ +1. Es ist jetzt 22:00 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 by Thomas Breitkreuz