![]() |
AW: Update-Vorgang in einen Thread auslagern
Die Kommunikation zwischen Threads und der Außenwelt geschieht über synchronisierte Events und geschützte Properties.
Der Thread ackert also im Hintergrund, möchte aber, das sein innerer Zustand (Was macht er gerade? Wie weit ist er?) in einer Form sichtbar ist. Fein. Dann unterhalten die sich eben, aber nicht direkt, bitte. Das ist praktisch, weil so eine Form auch gut für andere Threads verwendet werden kann und andererseits so ein Thread auch mit einer anderen Form reden kann (oder mit gar keiner, sollte ihm schnurz sein). Es bieten sich zum Unterhalten Events oder Interfaces an. Beides muss aber synchronisiert werden, d.h. Thread und Außenwelt laufen ja in unterschiedlichen Threads und die VCL ist nicht threadsafe, d.h. alles, was mit der VCL geschieht, muss im Hauptthread gemacht werden. Nehmen wir mal folgenden Thread;
Delphi-Quellcode:
Die fraglichen Methoden sind also 'ShowProgressBar', 'UpdateProgressBar' und 'HideProgressBar'.
Procedure TMyThread.Execute;
Begin ShowProgressBar(); MaxSteps := 1000; For i:=1 to MaxSteps do begin Self.Progress := I; UpdateProgressBar(); DoSomething(); end; HideProgressBar(); End; In jedem Fall müssen die erst einmal per 'Synchronize' (oder 'Queue') aufgerufen werden, denn diese beiden Methoden sorgen dafür, das die Aufrufe im Kontext des VCL-Threads durchgeführt werden. Also:
Delphi-Quellcode:
Ok. Nun gibt es (mindestens) zwei Ansätze, wie die konkrete Kommunikation mit einem Formular aussehen kann.
Procedure TMyThread.Execute;
Begin Synchronize(ShowProgressBar); MaxSteps := 1000; For i:=1 to MaxSteps do begin Self.Progress := I; Synchronize(UpdateProgressBar); DoSomething(); end; Synchronize(HideProgressBar); End; 1. Mit Events. Der Thread deklariert drei Eventhandler ,'OnShowProgressBar', 'OnHideProgressBar' und 'OnUpdateProgressBar' (geht auch mit einem Event, aber egal). Das Formular meldet sich auf die Events an und führt die VCL-Änderungen (Progressbar zeigen, verbergen, updaten) aus. So ungefähr
Delphi-Quellcode:
Oder mit einem Interface. Dabei erwartet der Thread eine 'View', die die drei Methoden zum Anzeigen der Progressbar bereitstellt.
Type
TMyThread = Class... public OnShowProgressBar : TNotifyEvent Read FOnShowProgressBar Write FOnShowProgressBar; OnHideProgressBar : TNotifyEvent Read FOnHideProgressBar Write FOnHideProgressBar ; ... end; Procedure TMyThread.ShowProgressbar; Begin if Assigned (FOnShowProgressBar) Then FOnShowProgressBar(Self); End; ... Procedure TMyForm.StartThread; Begin FmyThread := TmyThread.Create; FmyThread.OnShowProgressBar := ShowProgressBar; FmyThread.OnHideProgressBar := HideProgressBar; ... End; Procedure TMyForm.ShowProgressBar (Sender : TObject); Begin Progressbar.Visible := True; End;
Delphi-Quellcode:
Das geht alles viel einfacher und besser, aber das Prinzip sollte klar sein. Standardpattern bei Delphi sind die Events. In anderen Programmiersprachen eher die Interfaces (Java kennt z.B. gar keine Delegaten).
Type
IProgessBar = interface procedure ShowProgressBar; Procedure HideProgressBar; Procedure UpdateProgressBar (Position, Total : Integer); End; TMyThread = class (TThread) FProgess : IProgressBar; public Constructor Create (aProgress : IProgress); End; ... Procedure TMyThread.ShowProgressBar; Begin if Assigned (FProgress) then FProgess.ShowProgressBar; End; Procedure TMyThread.UpdateProgressBar; Begin if Assigned (FProgress) then FProgess.UpdateProgressBar (Self.Position, Self.Total); End; .... Type TMyForm = Class (TForm, IProgress) ... Procedure ShowProgressBar; Procedure HideProgressBar; Procedure UpdateProgressBar (Position, Total : Integer); End; |
AW: Update-Vorgang in einen Thread auslagern
Eigentlich ist das ein Fall für den asynchronen Prozeduraufruf.
Ältere Delphi-Versionen unterstützen das noch nicht. In jedem Fall muss der Hauptthread die Ereignisse selbst auslösen. A) Der Thread sendet Informationen über den Fortschritt mit Postmessage als Botschaft an ein Fenster, eine Komponente verarbeitet die Nachrichten und löst die Ereignisse aus. Es ist aber nicht zu 100% sichergestellt, das wirklich alle Nachrichten auch den Empfänger erreichen, insbesondere wenn sehr viele Nachrichten in kurzer Zeit anfallen. B) Der Thread legt Informationen über den aktuellen Progressstatus in einem geschützten Record ab, der Hauptthread prüft diesen Record periodisch (OnIDLE der Anwendung oder Timer). Dabei können aber auch einzelne Schritte übersprungen werden, insbesondere wenn der Thread sehr schnell arbeitet. C) Der Thread legt jede Ereignis z.B. als Objekt mit allen Informationen in einer geschützten Liste ab, der Hauptthread prüft die Liste periodisch (OnIDLE der Anwendung oder Timer). Der Aufwand ist wahrscheinlich größer, das scheint aber die sicherste Variante zu sein. |
AW: Update-Vorgang in einen Thread auslagern
Na, asynchron muss das Update der Progressbar hier nun doch nicht sein. Besser wäre es natürlich, aber es geht auch so.
Bei zeitkritischen Threads, die ihre Zeit nicht mit dem Update einer UI verplempern wollen, ist das natürlich anzuraten (wobei dann ein Timer in der UI noch einfacher umzusetzen ist. EDIT: Ach, das ist ja deine Antwort 'B'). |
AW: Update-Vorgang in einen Thread auslagern
Hallo !
Zitat:
Wie muss das in der Form1 dann aussehen (komplette Deklaration), ich meine damit durch den Thread ein Event in Form1 oder Form2, .... ausgelöst wird, das ist mir vollkommen unklar ? Ich denke damit kann man dann an mehreren Stellen im Programm auf Änderungen reagieren oder (ich denke hier z.B. an die Umstellung des Währungsformats, der Nachkommastellen, ....) ? |
AW: Update-Vorgang in einen Thread auslagern
Das mit dem Interface sieht natürlich klasse im Source aus...
Aber warum sendest Du nicht einfach aus dem Thread eine User-Message über Windows und schon sparst Du Dir das Syncronize!
Delphi-Quellcode:
Für Update einfach im WParam 0..100% übergeben und fertig.
Const
WM_PShow = WM_User + 400; WM_PHide = WM_USer + 401; WM_PUpdate = WM_USer + 402; type TMyForm = class(TForm) private procedure WMShowPBar(var Msg:Tmessage); message WM_PShow; procedure WMHidePBar(var Msg:Tmessage); message WM_PHide; procedure WMUpdatePBar(var Msg:Tmessage); message WM_PUpdate; end; Mavarik |
AW: Update-Vorgang in einen Thread auslagern
Hey Mavarik: Messages hab ich total vergessen. Sehr schöne Möglichkeit, speziell mit 'PostMessage' (asynchron)
Was mir daran als Pattern nicht so sehr gefällt ist die etwas problematische Übergabe komplexerer Informationen. Denn die müssen im Thread instantiiert und im Messagehandler (beim Postmessage) wieder freigegeben werden. Geht, ist sauber, aber imho nicht so schön (reine Geschmackssache) Was mir daran allerdings gefällt, ist der äußerst niedrige Footprint zum Erzeugen von asynchronen Updatemöglichkeiten. Zitat:
Delphi-Quellcode:
Type
TForm1 = class (TForm) ... Procedure UpdateProgressBar (Sender : TObject); // Das ist die Signatur eines TNotifyEvent ... Procedure TForm1.Button1Click(Sender : TObject); Begin FMyThread := TMyThread.Create; FMyThread.OnUpdateProgressBar := UpdateProgressbar; // Zuweisung des Eventhandlers an das Event ... Zitat:
|
AW: Update-Vorgang in einen Thread auslagern
Eventuell weil das zu stark mit der Plattform verbunden ist?
Weil man sich dafür einen MessageManager schreibt, den man dann für alles benutzen kann und vor allem verständliche und einfach zu handhabende Messages durch das System leiten kann. Auf den Trichter ist auch Emba gekommen ![]() Den Nachrichtentypen definieren:
Delphi-Quellcode:
Die Form damit verbinden
unit MyCustomMessages;
uses System.Messaging; type TProgressMessage = class( TMessage ) public constructor Create( Position, Max : Integer ); property Position : Integer read FProgress; property Max : Integer read FMax; end;
Delphi-Quellcode:
Der Thread schmeisst die Nachricht
uses
System.Messaging; TFoo = class( TForm ) Progressbar1 : TProgressBar; private procedure HandleProgressMessage( const Sender : TObject; const M : TMessage ); public procedure AfterConstruction; override; procedure BeforeDestruction; override; end; implementation uses MyCustomMessages; procedure TFoo.AfterConstruction; begin inherited; TMessageManager.DefaultManager.SubscribeToMessage( TProgressMessage, HandleProgressMessage ); end; procedure TFoo.BeforeDestruction; begin TMessageManager.DefaultManager.Unsubscribe( TProgressMessage, HandleProgressMessge ); inherited; end; procedure TFoo.HandleProgressMessage( const Sender : TObject; const M : TMessage ); var LMsg : TProgressMessage absolute M; begin ProgressBar1.Position := LMsg.Position; ProgressBar1.Max := LMsg.Max; end;
Delphi-Quellcode:
Und schon klappt das auf jeder Plattform, auch ohne irgendwelche Fenster (Handle) offen zu haben, egal wer das empfangen möchte, kann sich einfach an den MessageManager hängen und alles ist gut.
uses
System.Messaging, MyCustomMessages; procedure TFooThread.Execute; var LPosition, LMax : Integer; begin while WorkInProgress do begin // Fortschritts-Nachricht verschicken Queue( procedure begin TMessageManager.DefaultManager.SendMessage( Self, TProgressMessage.Create( LPosition, LMax ) ); end ); ... end; end; |
AW: Update-Vorgang in einen Thread auslagern
Zitat:
Selbst wenn es ein mehr ist.. im thread ein guter alter Getmem und im Form der Freemem. Da man das kaum noch verwendet - ist sofort klar - Oh ein Getmem... Da muss sich jemand anderes darum kümmern das wieder frei zu geben... Mavarik |
AW: Update-Vorgang in einen Thread auslagern
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:
Aber er sprach von einem eigenen Installer - Sieht mir nicht nach iOS/Android oder Mac aus...:stupid: Also ist es - erst mal - nicht so schlimm, wenn es nur für Windows ist. Abgesehen davon spart man sich das Queue...:twisted: Mavarik PS.: Du schreibst jetzt 100x "Ich soll keinen kompletten Sourcecode im Forum posten" |
AW: Update-Vorgang in einen Thread auslagern
Zitat:
Aber ansonsten hast Du natürlich vollkommen recht. Dein Ansatz ist absolut gleichrangig und reine Geschmackssache (oder Starrsinn), das ich es nicht verwende. Also: bisher 4 schöne Pattern, um Aktualisierungen aus einem Thread in einen anderen zu übermitteln: 1. Events 2. Interfaces 3. Messages 4. Observer-Pattern (@Sir Rufo, einverstanden?) |
Alle Zeitangaben in WEZ +1. Es ist jetzt 08:26 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