Delphi-PRAXiS
Seite 2 von 2     12   

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)

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 12:15 Uhr.
Seite 2 von 2     12   

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