Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Threads verwalten (https://www.delphipraxis.net/143693-threads-verwalten.html)

David Martens 21. Nov 2009 00:17


Threads verwalten
 
Ich bin dabei ein Programm zu schreiben das verschiedene Dateien auf Änderungen überwacht. Jetzt will ich das aber in verschiedenen Threads machen. Meine Frage wie?

ein Thread soll haben:
- 1 Komponente zur Überwachung (TfisFileNotification)
- 1 Komponente zur Speicherung (Versionierung) der Änderung (TJvZlibMultiple)
- dirverse einfache Variablen wie:
Delphi-Quellcode:
   
    Name,
    WatchFolder,
    BackupFolder  : string;

    IncludeFiles,
    ExcludeFiles,
    ExcludeFolder : string;
    minSize,
    maxSize       : integer;
Nun weiß ich aber im Vorfeld nicht wieviele Threads ich brauche. Ich dachte da an eine TObjectList am besten eine typisierte (siehe hier).

Da ich aber noch nicht so tief in die Materie eingestiegen bin wollte ich wissen wie ich Threads in eine Liste verwalten kann.
Die Threads sind von Natur aus (vielleicht bis auf die, von mir gewünschten, Meldungen fürs Log) unabhänig. Da habe ich schon was gefunden mit
Delphi-Quellcode:
PostMessage(MainForm.Handle, MY_WM_USER, SUB_MESSAGE_X, Integer(InfoRecord));
kann ich aus den einzelnen Threads die Log füllen.

Was ich jetzt noch wissen muß wie muß die Threadklasse aussehen, wie kann ich die zur Laufzeit erzeugen und wie verwalte ich die in der Liste?

Ich hoffe ich habe mich verständlich ausgedrückt und falls das alles Quatsch ist, was ich geschrieben habe dann entschuldige ich mich schonmal.

Danke
David

sirius 22. Nov 2009 15:08

Re: Threads verwalten
 
Hmm, ist ja eine sehr allgemeine Frage.

Also, ja Du kannst viele Threads erzeugen und ale einer Objectliste hinzufügen. Ob du die Liste typisieren musst, liegt an dir und der Aufgabe selbst.

Die Übergabe des Ergebnisses würde per Postmessage gehen, aber dazu muss InfoRecord extern auf dem Heap liegen, oder du verwnedest sendmessage.

taaktaak 22. Nov 2009 15:16

Re: Threads verwalten
 
PostMessage und SendMessage verwende ich auch sehr oft, darf ich da mal eine Zwischenfrage stellen:
Wie erreiche ich, dass InfoRecord auf dem Heap eingerichtet wird und damit PostMessage verwendet werden kann? Wird eine Unit-lokale Variable im Heap angelegt?

Apollonius 22. Nov 2009 15:21

Re: Threads verwalten
 
Nein, eine Unit-lokale Variable ist ja auch global. Das Problem daran ist, dass es die Variable nur einmal gibt - du musst also sicherstellen, dass du nicht die Daten überschreibst, bevor der Hauptthread sie abgeholt hat. Für den Heap brauchst du New und Dispose bzw. GetMem und FreeMem.

himitsu 22. Nov 2009 15:24

Re: Threads verwalten
 
unitlokal/global = heap
prozedurlokal = stack

du mußt aber aufpassen, daß der Speicher nicht wieder überschrieben wird.

z.B. mehrmals PostMessage mit diesem Speicher, aber das "Ziel" hat die gesendeten Nachrichten noch garnicht verarbeitet und somit würde der Speicher zurüh überschrieben.

Du kannst aber z.B. mit GetMem Speicher reservieren, mit PostMessage versenden und am "Ziel" diesen Speicher dann wieder freigeben.
(aber Achtung: einige Speichermanager muß man erst in einen Threadsicheren Modus versetzen, aber ich hoffe mal, daß dieses bei dir schon der Fall ist)

Apollonius 22. Nov 2009 15:27

Re: Threads verwalten
 
Hm? "Heap" ist doch normalerweise nur der Bereich, der vom Speichermanager verwaltet wird, und das ist bei globalen Variablen nicht der Fall. Kann aber sein, dass ich mich hier in der Terminologie irre.

taaktaak 22. Nov 2009 15:35

Re: Threads verwalten
 
Danke, bisher funktioniert das alles recht gut!
Meine Anwendungsfälle sind allerdings nicht thread-bezogen, ich verwende das Verfahren um Nachrichten zwischen Units auszutauschen, die sich nicht kennen. So versendet z.B. die Konfiguration eine neu gesetzte Farbe und alle Units, die sich berufen fühlen, aktualisieren dann ausgewählte Controls.

Äh, ja, ein Widerspruch ist da.
Würde aber aufgrund fehlender Fehler (hihi) zu himitsu's Erklärung tendieren

David Martens 22. Nov 2009 23:49

Re: Threads verwalten
 
Um auf die ursprüngliche Frage zurückzukommen, ja die Frage ist sehr allgemein.

So weit ich jetzt verstanden habe kann ich also eine Klasse wie die erstellen:
Delphi-Quellcode:
TWatchThread = class(TThread)
private
  FileNotify : TfisFileNotification;
  FileZip   : TJvZlibMultiple;

  Name,
  WatchFolder,
  BackupFolder  : string;
  IncludeFiles,
  ExcludeFiles,
  ExcludeFolder : string;
  minSize,
  maxSize       : integer;

  procedure OnNotifyChanged(Sender: TObject);
public
  constructor Create(AOwner : TComponent);
 
  procedure Execute; override;
end;

procedure TWatchThread.Execute;
begin
  FileNotify.Start;
  ...
  FlieNotify.OnChanged := OnNotifyChanged;
end;

procedure TWatchThread.OnNotifyChanged(Sender: TObject);
begin
  // auf den Event reagieren
  // Liste der Dateien durchgehen, Änderungen mit FileZip speichern
 
  // So hier die Meldung für das Log ???
  PostMessage(MainForm.Handle, MY_WM_USER, SUB_MESSAGE_NEW_SAVE, Integer(InfoRecord));
end;
Und dann kann ich die TWatchThread Objekte einfach in eine TObjectList speichern?

Delphi-Quellcode:
// Threads erstellen
begin
  ObjectList := TObjectList.Create;
  ObjectList.OwnsObjects := true;

  // für alle zu überwachende Verzeichnisse
  ObjectList.Add(TWatchThread.Create(Self));
end;

// Threads starten
begin
  aWatchThread := TWatchThread(ObjectList.Items[index]);
  aWatchThread.Execute;
end;
Ich muß natürlich noch auf die Messages reagieren, aber hab ich das so richtig gemacht?

Danke David

himitsu 23. Nov 2009 07:31

Re: Threads verwalten
 
Man ruft .Execute nicht direkt auf!
Und wo wie/wo ist InfoRecord definiert?

David Martens 23. Nov 2009 16:41

Re: Threads verwalten
 
Delphi-Quellcode:
 
  PInfoRecord = ^TInfoRecord ;
  TInfoRecord = record
                   WatchThreadID : Cardinal;
                   Meldung : string;
                 end;

procedure TWatchThread.OnNotifyChanged(Sender: TObject);
var
  InfoRecord : PInfoRecord;
begin
  // auf den Event reagieren
  // Liste der Dateien durchgehen, Änderungen mit FileZip speichern
 
  // Also so ???
  New(FoundRecord);
  InfoRecord^.WatchThreadID := GUID;
  InfoRecord^.Meldung      := 'Hier kommt der Text für die Meldung rein';
 
  PostMessage(MainForm.Handle, MY_WM_USER, SUB_MESSAGE_NEW_SAVE, Integer(InfoRecord));
end;

procedure TMainForm.ThreadMessage( var Message : TMessage );
var
  InfoRecord : PInfoRecord;
  ID : Cardinal;
begin
  case Message.WParam of

    MY_WM_USER :
      begin
        InfoRecord := PInfoRecord(Message.LParam);
        ID := ThreadIDToIndex( InfoRecord^.WatchThreadID );
        Memo.Lines.Add(InfoRecord^.Meldung);
        Dispose(InfoRecord);
      end;

end;
Sohier dachte ich.

sirius 23. Nov 2009 17:25

Re: Threads verwalten
 
InfoRecord ist ok. (Um noch sicherer zu gehen, sollte man auf den Rückgabewert von PostMessage reagieren, sonst läuft man Gefahr, dass ein MemLeak entsteht)

Aber nicht Mainform.Handle verwenden. Lieber das Handle mit an den Thread übergeben und dort aus privates Feld speichern.

taaktaak 23. Nov 2009 17:55

Re: Threads verwalten
 
Zwischenfrage:

Da PostMessage die Nachricht in die Warteschlage ablegt, kann der Rückgabewert doch nur den Erfolg der Nachrichten-Ablage verkünden - damit wird nicht ausgesagt, dass der Empfänger sie bereits erhalten hat - korrekt??

sirius 23. Nov 2009 18:01

Re: Threads verwalten
 
Zitat:

Zitat von taaktaak
Zwischenfrage:

Da PostMessage die Nachricht in die Warteschlage ablegt, kann der Rückgabewert doch nur den Erfolg der Nachrichten-Ablage verkünden - damit wird nicht ausgesagt, dass der Empfänger sie bereits erhalten hat - korrekt??

Richtig. Aber wenn aus irgendeinem Grund die Nachricht nicht in die Nachrichtenschlange gesetzt werden kann, wird false zurückgeliefert und man sollte den Speicher selber freigeben.
Wenn der Empfänger allerdings die Nachricht einfach nur wegschmeißt oder vor dem Dispose eine Exception auftritt (->> besser try finally verwenden), kann man bei dieser Art Kommunikation nichts machen.

David Martens 23. Nov 2009 20:47

Re: Threads verwalten
 
Soweit bin ich jetzt:

Delphi-Quellcode:
const
  WM_MY_USER = WM_USER + 101;

type
  PInfoRecord = ^TInfoRecord ;
  TInfoRecord = record
                   GUID : string;
                   Meldung : string;
                 end;

  TWatchThread = class(TThread)
  private
    fHandle : HWND;

    procedure fisFileNotifyDirectoryChanged(Sender: TObject);
  public
    OneBackup : TOneBackup;
    FileNotify : TfisFileNotification;
    FileZip   : TJvZlibMultiple;

    procedure StartWatch;
  protected
    procedure DoTerminate; override;
  public
    constructor Create(aOwner : TComponent; Handle : HWND); virtual;
  end;

  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    Button2: TButton;
    Edit1: TEdit;
    Button3: TButton;

    procedure OnEvent( var Message : TMessage ); message WM_MY_USER;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    ObjectList : TObjectList;
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TWatchThread.DoTerminate;
begin
  inherited;

  FileNotify.Stop;
end;

procedure TWatchThread.fisFileNotifyDirectoryChanged(Sender: TObject);
var
  InfoRecord : PInfoRecord;
begin
  if not Terminated then
  begin
    New(InfoRecord);
    InfoRecord^.GUID   := OneBackup.GUID;
    InfoRecord^.Meldung := OneBackup.Name + ' - ' + FormatDateTime('hh:nn:ss.zzz', now) + ' - ' +
                           'FileNotify.MessageNo';

    PostMessage(fHandle, WM_MY_USER, 1, Integer(InfoRecord));

    // hier müssen die Änderungen abgespeichert werden
    // funktioniert noch nicht
//    FileZip.CompressDirectory(OneBackup.WatchFolder,
//                              OneBackup.IncludeSub,
//                              OneBackup.BackupFolder + '\' + OneBackup.GUID + '.zip');
  end;
end;

procedure TWatchThread.StartWatch;
begin
  FileNotify.Subtree := OneBackup.IncludeSub;
  FileNotify.Start;
end;

constructor TWatchThread.Create(aOwner : TComponent; Handle : HWND);
var
  NewGUID: TGUID;
begin
  inherited Create(true);
  freeOnTerminate := true;

  fHandle := Handle;

  OneBackup := TOneBackup.Create;

  FileNotify := TfisFileNotification.Create(aOwner);
  FileNotify.OnDirectoryChanged := fisFileNotifyDirectoryChanged;

  with OneBackup do
  begin
    CreateGUID(NewGUID);
    GUID := GUIDToString(NewGUID);

    Name          := '';
    WatchFolder   := 'C:\Test\Treadtest'; // nur mal zum Testen
    BackupFolder  := 'C:\Test';           // nur mal zum Testen

    ...

    FileNotify.Directory := WatchFolder;
    FileNotify.NotificationType := [noFilename, noDirname, noSize, noLastWrite, noCreation];
  end;

  FileZip := TJvZlibMultiple.Create(aOwner);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  WatchThread : TWatchThread;
begin
  WatchThread := TWatchThread.Create(Self, Self.Handle);

  // hier die Daten übergeben
  WatchThread.OneBackup.Name := Edit1.Text;

  TWatchThread(ObjectList.Items[ObjectList.Add(WatchThread)]).Start;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  i : integer;
begin
  for i := 0 to ObjectList.Count - 1 do
  begin
    TWatchThread(ObjectList.Items[i]).Terminate;
  end;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  TWatchThread(ObjectList.Items[ObjectList.Count - 1]).StartWatch;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ObjectList := TObjectList.Create;
  ObjectList.OwnsObjects := false;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  ObjectList.Free;
end;

procedure TForm1.OnEvent(var Message : TMessage);
var
  InfoRecord : PInfoRecord;
  ID : string;
begin
  case Message.Msg of

    WM_MY_USER :
      begin
        try
          InfoRecord := PInfoRecord(Message.LParam);
          ID := InfoRecord^.GUID;
          Memo1.Lines.Add(InfoRecord^.Meldung);
        finally
          Dispose(InfoRecord);
        end;
      end;
  end;
end;
Das Ganze funktioniert schon ganz gut nur die Speicherung und das Logging muß jetzt noch rein.

Ich werde aber die Komponenten für FileNotify und FileZip nochmal austauschen.
Die Gründe:
- TfisFileNotification sagt mir nicht welche Dateien geändert wurden, mit SHChangeNotify geht das
- TJvZlibMultiple scheint mir zu unflexibel (bzw. kein kompatibles Zipformat), mit TAbZipKit geht es vielleicht besser

Ich muß mir auch noch eine Struktur überlegen wie ich die Änderungen speichere.

Jetzt erst einmal werde ich, bei geder Änderung, alle Dateien in eine neue Zip-Datei speichern. Das ist zwar nicht so schön, aber reicht für meine Zwecke. Ich will ja nur den Fortschritt meiner Diplomarbeit dokumentieren und sichern und das möglichst auf einem USB-Stick/HDD.

Ich benutze TeXnicCenter und MiKTeX 2.8 in einem Mojo-Pack (das geht erstaunlich gut), solange keine vernünftige Portable Version raus ist und werde die tex-Dateien mit dem kleinen Programm zusätzlich auf dem USB-Stick speichern und zu Hause auf den Rechner kopieren. :) Das sollte reichen.

uoeb7gp 23. Nov 2009 23:23

Re: Threads verwalten
 
Hallo David Martens.

Also folgendes.

>> New(InfoRecord);
Ist nur innerhalb des Threadkontext (Threadobjekt) gültig.
Es handelt sich um lokalen Speicher, dies funktioniert
bei deinem Source nur weil Du keinen Thread erzeugst,
Wo ist das Execute???

Für Globalen Memory musst Du zB. GlobalAlloc verwenden, dieser Memory kann dann auch vom MainThread der Applikation
ohne Probleme verwendet werden.


>> PostMessage(fHandle, WM_MY_USER, 1, Integer(InfoRecord));

Ist nur solange in einer multithreaded Umgebung Gültig, solange kein weiteres Event kommt.
ZB. Event wird vom OS getriggert, thread hat noch Rechenzeit übrig, und es kommt innerhalb dieser noch zu
einem zusätzlichen Event, dann überschreibst Du den vorhergehenden Record, da Du diesen Asynchron
mit PostMessage in die MQ gepostet hast.
Hier musst Du Send Message verwenden.


>> FileZip := TJvZlibMultiple.Create(aOwner);

Hier musst Du berücksichtigen, dass VCL-Komponenten, die Du im Mainthread erzeugst, nicht Threadsave sind!
Syncronize, bzw. Sperrobjekte verwenden!

>> ObjectList : TObjectList;

Delphi-Quellcode:
 
  for i := 0 to ObjectList.Count - 1 do
  begin
    TWatchThread(ObjectList.Items[i]).Terminate;
  end;
Hier hilft Terminate alleine nicht, da du blockieren auf ein Event wartest, hier musst du nach dem
Terminate, noch das FileNotify - Handle schließen, sonst terminiert der Thread nicht.

Sieh Dir am besten mal das Thread Demo das mit Delphi mitgeliefert wird an.

lg.

sirius 24. Nov 2009 08:01

Re: Threads verwalten
 
Zitat:

Zitat von uoeb7gp
>> New(InfoRecord);
Ist nur innerhalb des Threadkontext (Threadobjekt) gültig.

Das stimmt nicht. Es funktioniert genauso. Das ist ja genau der Sinn bei Threads, sie haben denselben Speicher. Nur der Stack ist für jeden Thread separat.
Selbst in den Delphi-Sourcen wird gelegentlich dieser Weg gegangen.
Nichtsdestotrotz gibt es bessere Varianten, aber da kann man bei Threads ewig streiten.

Zitat:

Zitat von uoeb7gp

>> PostMessage(fHandle, WM_MY_USER, 1, Integer(InfoRecord));

Ist nur solange in einer multithreaded Umgebung Gültig, solange kein weiteres Event kommt.
ZB. Event wird vom OS getriggert, thread hat noch Rechenzeit übrig, und es kommt innerhalb dieser noch zu
einem zusätzlichen Event, dann überschreibst Du den vorhergehenden Record, da Du diesen Asynchron
mit PostMessage in die MQ gepostet hast.

Stimmt auch nicht. Genau deswegen arbeitet er ja mit New und Dispose. Das nächste Event erstellt einen neuen Record. Der alte ist immernoch gültig.


Zitat:

Zitat von uoeb7gp
[
>> FileZip := TJvZlibMultiple.Create(aOwner);

Hier musst Du berücksichtigen, dass VCL-Komponenten, die Du im Mainthread erzeugst, nicht Threadsave sind!
Syncronize, bzw. Sperrobjekte verwenden!

Ich kenne TJvZlibMultiple nicht, aber ist es wirklich ein visuelles Objekt? Sieht mir eher nicht so aus. Die Threadsicherheit sollte man überprüfen, ich würde aber erstmal davon ausgehen, dass es sich nur um Dateiarbeit handelt und da IMHO nie Probleme mit Threads auftauchen.


Zitat:

>> ObjectList : TObjectList;

Delphi-Quellcode:
 
  for i := 0 to ObjectList.Count - 1 do
  begin
    TWatchThread(ObjectList.Items[i]).Terminate;
  end;
Hier hilft Terminate alleine nicht, da du blockieren auf ein Event wartest, hier musst du nach dem
Terminate, noch das FileNotify - Handle schließen, sonst terminiert der Thread nicht.
Genau deswegen wird ja eben nichtSendMEssage verwendet. Postmessage arbeitet asynchron. Dadurch entsteht hier kein Problem. (in diesem speziellen Fall würde nichtmal eins mit SendMessage entstehen).


Wo ich ein Problem sehe, ist:
  • In Do Terminate wird Filexxx.Stop aufgerufen. Das muss evtl. synchronisiert werden (kenne das dahinter liegende Objekt nicht).
  • Es werden zuviele Klassen in TThread.Create erstellt. Hier muss man ausnahmsweise mal mehr in Execute packen (erstellen, und löschen). Kann ich aber im Detail nicht weiter beurteilen, da mir der restliche Code sowie die verwendeten Komponenten fehlen.

uoeb7gp 24. Nov 2009 10:28

Re: Threads verwalten
 
Hallo sirius, hast Recht, war da total auf dem Holzweg (oder sonst wo?)!!!

Bei der >> ObjectList : TObjectList;, hab ich aber nicht das SendMessage
sondern das FileNotyfy Event gemeint, dies Blockiert ja, wenn sich im
Filesystem nichts ändert. Dh. Thread ist Terminated, aber dieser kann sich nicht
beenden wenn das FileNotify-Handle nicht geschlossen wird.

@David Martens hab ein tolles Tutorial gefunden, hab mich draus auch mal schlau gemacht "g"!

http://www.michael-puff.de/Developer...mit_Delphi.pdf

lg.

sirius 24. Nov 2009 10:39

Re: Threads verwalten
 
Zitat:

Zitat von uoeb7gp
Bei der >> ObjectList : TObjectList;, hab ich aber nicht das SendMessage
sondern das FileNotyfy Event gemeint, dies Blockiert ja, wenn sich im
Filesystem nichts ändert. Dh. Thread ist Terminated, aber dieser kann sich nicht
beenden wenn das FileNotify-Handle nicht geschlossen wird.

Ja, das stimmt, wenn man so einen blockierenden Befehl hat, muss man bei Terminate diesen auch lösen. Ich dachte das macht FileNotify.Stop? Bin mir aber nicht sicher. Das sollte man dann aber auch nicht in "DoTerminate" sondern IMHO in einer überschriebenen "Terminate"-Methode machen.
--> also irgendiwe muss die Blockierung gelöst werden.

David Martens 25. Nov 2009 14:06

Re: Threads verwalten
 
Danke erstmal für die tolle Unterstützung.

Ja, ich hab vergessen die Objekte freizugeben. Wo mach ich das am besten im DoTerminate oder destructor von TWatchThread?
Ich würde die in den destructor packen, weil ich sie ja auch im constructor erstelle.

Zu den beiden VCL-Komopnenten, die sind nicht visuell (auch die Alternativen).

@sirius:
1. Warum muß das Filexxx.Stop eventuell synchronisiert werden? Das sagt der Komponente nur das es aufhören soll Events auszulösen, wennn an dem beoachteten Ordner Änderungen vorgenommen werden. Das sollte nicht zeitkritisch sein.
2. Warum soll ich das Erstellen der Komponenten ins Execute packen? Beim Erstellen passiert ja noch nicht viel, erst beim Starten (Filexxx.Start und Starten des Zipvorganges) legen die los.

@uoeb7gp:
Zitat:

Bei der >> ObjectList : TObjectList;, hab ich aber nicht das SendMessage
sondern das FileNotyfy Event gemeint, dies Blockiert ja, wenn sich im
Filesystem nichts ändert. Dh. Thread ist Terminated, aber dieser kann sich nicht
beenden wenn das FileNotify-Handle nicht geschlossen wird.
Moment, der FileNotyfy Event blockiert doch nicht. Im Grunde ist das auch nichts anderes als auf eine Message zu warten; Da blockiert nichts. Wenn ich auf eine Eingabe/Button-Klick warte blockiert das ein Programm ja auch nicht. :gruebel:

Das fisFileNotifyDirectoryChanged soll ja nur die Meldung für das Log absetzen und den Backup-Vorgang starten. Später kommen dann noch weitere Funktionen hinzu (detaillierter Datei(namen)test und Meldungen für Backup-start und Backup-erfolgreich/abgebrochen)

sirius 25. Nov 2009 14:23

Re: Threads verwalten
 
Zitat:

Zitat von David Martens
1. Warum muß das Filexxx.Stop eventuell synchronisiert werden? Das sagt der Komponente nur das es aufhören soll Events auszulösen, wennn an dem beoachteten Ordner Änderungen vorgenommen werden. Das sollte nicht zeitkritisch sein.

Nur ist gut. Die Frage ist wie? Ich weis es eben nicht. Vielleicht wird nur ein Flag gesetzt, dann dürfte es gut gehen.

Zitat:

2. Warum soll ich das Erstellen der Komponenten ins Execute packen? Beim Erstellen passiert ja noch nicht viel, erst beim Starten (Filexxx.Start und Starten des Zipvorganges) legen die los.
Du redest von Messages etc. Das setzt voraus, dass diese Komponenten ein (unsichtbares) Fenster haben. Und diese müssen auch immer in dem Thread erstellt werden, in welchen sie benutzt werden. Und der Thread startet erst nach dem Constructor.
Diese Könnte neben den Fenstern auch noch bei anderen Systemressourcen der Fall sein.


Alle Zeitangaben in WEZ +1. Es ist jetzt 09:03 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz