![]() |
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:
Nun weiß ich aber im Vorfeld nicht wieviele Threads ich brauche. Ich dachte da an eine TObjectList am besten eine typisierte (Name, WatchFolder, BackupFolder : string; IncludeFiles, ExcludeFiles, ExcludeFolder : string; minSize, maxSize : integer; ![]() 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:
kann ich aus den einzelnen Threads die Log füllen.
PostMessage(MainForm.Handle, MY_WM_USER, SUB_MESSAGE_X, Integer(InfoRecord));
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 |
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. |
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? |
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.
|
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) |
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.
|
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 |
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:
Und dann kann ich die TWatchThread Objekte einfach in eine TObjectList speichern?
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;
Delphi-Quellcode:
Ich muß natürlich noch auf die Messages reagieren, aber hab ich das so richtig gemacht?
// 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; Danke David |
Re: Threads verwalten
Man ruft .Execute nicht direkt auf!
Und wo wie/wo ist InfoRecord definiert? |
Re: Threads verwalten
Delphi-Quellcode:
Sohier dachte ich.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; |
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. |
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?? |
Re: Threads verwalten
Zitat:
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. |
Re: Threads verwalten
Soweit bin ich jetzt:
Delphi-Quellcode:
Das Ganze funktioniert schon ganz gut nur die Speicherung und das Logging muß jetzt noch rein.
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; 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. |
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:
Hier hilft Terminate alleine nicht, da du blockieren auf ein Event wartest, hier musst du nach demfor i := 0 to ObjectList.Count - 1 do begin TWatchThread(ObjectList.Items[i]).Terminate; end; 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. |
Re: Threads verwalten
Zitat:
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:
Zitat:
Wo ich ein Problem sehe, ist:
|
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"! ![]() lg. |
Re: Threads verwalten
Zitat:
--> also irgendiwe muss die Blockierung gelöst werden. |
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:
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) |
Re: Threads verwalten
Zitat:
Zitat:
Diese Könnte neben den Fenstern auch noch bei anderen Systemressourcen der Fall sein. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 12:05 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