![]() |
TThread.Queue - Datenübergabe
Hi, ich nutze bisher immer einfache Events in Thread um die Daten an den Hauptthread zu übergeben. Das möchte ich jetzt mal mit Queues machen.
ein Thread sieht bei mir z.B. so aus:
Delphi-Quellcode:
Er soll eine Userliste aus eine Datenbank holen, und mir an meine Hauptthread übergeben.
unit uSQLThread.SQLGetUser;
interface uses System.Classes, System.SysUtils, System.Types, uUser, uCallings, ZAbstractConnection, ZConnection, ZAbstractRODataset, ZDataset, Data.DB, uDBSettings; type TOnThreadFinished=procedure(Sender: TObject) of object; TOnUpdateUserList=procedure(Sender: TObject; UserList: TUserList) of object; TSQLGetUser=class(TThread) private FViewRange: TViewRange; FStartdate, FEnddate: TDateTime; FDBSettings: TDBSettings; FOnThreadFinished: TOnThreadFinished; FOnUpdateUserList: TOnUpdateUserList; procedure DoThreadFinished; procedure DoUpdateUserList(UserList: TUserList); published property OnThreadFhinished: TOnThreadFinished read FOnThreadFinished write FOnThreadFinished; property OnUpdateUserList: TOnUpdateUserList read FOnUpdateUserList write FOnUpdateUserList; public constructor Create(Suspended: Boolean; DBSettings: TDBSettings; ViewRange: TViewRange; Startdate, Enddate: TDateTime); protected procedure Execute; override; end; const DebuggingName = 'SQLGetUser'; implementation { TSQLTemplate } constructor TSQLGetUser.Create(Suspended: Boolean; DBSettings: TDBSettings; ViewRange: TViewRange; Startdate, Enddate: TDateTime); begin inherited Create(Suspended); FDBSettings:=TDBSettings.Create; DBSettings.AssignTo(FDBSettings); self.FViewRange:=ViewRange; self.FStartdate:=Startdate; self.FEnddate:=Enddate; end; procedure TSQLGetUser.DoThreadFinished; begin if Assigned(FOnThreadFinished) then Synchronize(procedure begin FOnThreadFinished(Self); end); end; procedure TSQLGetUser.DoUpdateUserList(UserList: TUserList); begin if Assigned(FOnUpdateUserList) then Synchronize(procedure begin FOnUpdateUserList(Self, UserList); end); end; procedure TSQLGetUser.Execute; var FConnection: TZConnection; FUserList: TUserList; begin self.NameThreadForDebugging(DebuggingName); Self.FreeOnTerminate:=True; FConnection:=TZConnection.Create(nil); FUserList:=TUSerList.Create(True); try FConnection.HostName:=FDBSettings.Hostname; FConnection.Port:=FDBSettings.Port; FConnection.User:=FDBSettings.UserName; FConnection.Password:=FDBSettings.Password; FConnection.Protocol:=FDBSettings.Provider; FConnection.Database:=FDBSettings.Databasename; FConnection.LoginPrompt:=False; FConnection.Connect; if not Self.Terminated then begin FUserList.LoadFromDB(FConnection, FViewRange, FStartdate, FEnddate); DoUpdateUserList(FUserList); end; finally FUserList.Free; FConnection.Free; FDBSettings.Free; DoThreadFinished; end; end; end. Der Aufruf erfolgt so:
Delphi-Quellcode:
Wie verpacke ich das jetzt in eine Queue? Ich kann ja der Queue ein Methode übergeben.
procedure TfrmMOMain.UpdateUserList;
var SQLGetUser: TSQLGetUser; begin SQLGetUser:=TSQLGetUser.Create(True, FDBSettings, FViewRange, FStartDate, FEndDate); SQLGetUser.OnUpdateUserList:=SQLUpdateUserList; SQLGetUser.Resume; end; procedure TfrmMOMain.ThreadUpdateUserlist(sender: TObject; ThreadUserlist: TUserList); begin ThreadUserlist.AssignTo(UserList); BuildUserList; end; Wäre dann Anstelle von
Delphi-Quellcode:
ein
DoUpdateUserList(FUserList);
Delphi-Quellcode:
korrekt und ich könnte dann den Event "TOnUpdateUserList" weglassen?
Queue(nil, frmMOMain.ThreadUpdateUserlist(FUserList));
|
AW: TThread.Queue - Datenübergabe
Bei einer Funktion/Methode garnicht, da ist es genau so, wie es schon seit Jahrzehnten für Synchronize in allen Beispielen gezeigt wird.
"globale" variablen :stupid: Aber bei einer anonymen Methode kann man einfach Variablen durchreichen.
Delphi-Quellcode:
Hier wäre es zu praktich, wenn man Queue/Syncronize einen Data-Parameter mitgeben könnte, so wie man es von anderen "Callbacks" kennt, was es hier aber nicht gibt.
procedure Test;
var S: string; // diese lokale Variable wird von Delphi in ein Interface verpackt und alle Prozeduren nutzen eine Referenz darauf begin S := 'Hallo Welt'; TThread.Syncronize(nil, procedure begin S := S + '!'; end); TThread.Queue(nil, procedure begin ShowMessage(S); end); end; |
AW: TThread.Queue - Datenübergabe
Leider ist das bei mir noch nicht so ganz angekommen.
Heißt das jetzt, dass ich das mit einer Queue gar nicht machen kann? Ich hab noch nicht so ganz raus, wie das mit den Queues funktioniert. In meinem Fall beinhaltet die Userlist die Daten hinter meinem VirtualListView. Für eine Aktualisierung lass ich alles Zeitaufwendige vom Thread erledigen, und erst wenn alles fertig bereit im Speicher liegt, übergebe ich die Daten an meine Userlist der Form und aktualisiere dann mein Virtuallistview. Ich hatte mir das so vorgestellt, die UserList vom Thread an die Queue des MainThreads zu übergeben, und dann, wenn der Mainthread mal zeit hat, kann er ja die Aktualisierung vornehmen. Der Thread soll jetzt aber nicht darauf warten müssen (so wie es ja zur Zeit durch das Synchronize ist). Logischerweise, da TThread.Queue ja asynchron arbeitet, wären natürlich die Daten aus dem Thread gar nicht mehr da, wenn der Mainthread sich endlich damit befassen könnte. Mein Gedanke war jetzt, dass man der Queue auch Daten mitgeben könnte, die sie quasi nur diese eine Prozedur behält, und danach verwirft. (Klingt ein bisschen bescheuert, wie ich mich jetzt ausgedrückt habe). Wenn ich dich also richtig verstehe, dann taugt eine Queue quasi nur als "Signalgeber" bzw. Trigger für irgendwas "datenloses". Oder anders ausgedrückt. Ich erstelle mir im Hauptthread nehmen meiner UserList eine zweite Instanz, die aber nur für den Thread da ist. Diese zweite Instanz wird vom Thread mit Daten gefüllt, und über eine Queue teile ich dem Hauptthread nur mit "Bin fertig". Danach kann der Hauptthread mit zweiten Instanz machen was er will. |
AW: TThread.Queue - Datenübergabe
Das Problem hier ist eher deine UserList-Instanz, die du der Queue irgendwie mitgeben willst. Queue blockt ja nicht und der darauf folgende Thread-Code wird quasi direkt nach dem Aufruf ausgeführt. Dieser gibt in deinem Fall als erstes die UserList frei, was zu einem Fehler im Hauptthread führt.
Mit einem temporären String kann man das aber lösen:
Delphi-Quellcode:
procedure TSQLGetUser.DoUpdateUserList(UserList: TUserList);
var tmpString: string; begin if Assigned(FOnUpdateUserList) then begin tmpString := UserList.CommaText; TThread.Queue(nil, procedure var tmpList: TStringList; begin tmpList := TStringList.Create; try tmpList.CommaText := tmpString; FOnUpdateUserList(Self, tmpList); finally tmpList.Free; end; end); end; end; |
AW: TThread.Queue - Datenübergabe
Zitat:
Also müsste das ja äquivalent funktionieren:
Delphi-Quellcode:
Okay, die Methode ist anonym. Aber zu welchem Zeitpunkt wird sie ausgeführt? Die Daten werden ja erst innerhalb der anonymen Methode zugewiesen (ist in deinem Beispiel mit der TStringlist ja auch so). Ich nahm jetzt aber an, dass die Queue des Hauptthread selber entscheidet, wann die Methode ausgeführt wird. Und das könnt ja dann auch wieder sein, wenn die Daten (in meinem Beispiel MyUserList) bereits wieder zerstört sind.
procedure TSQLGetUser.DoUpdateUserList(UserList: TUserList);
var MyUserList: TUserList; begin if Assigned(FOnUpdateUserList) then begin MyUserList:=TUserList.Create(True); Try UserList.AssignTo(MyUserlist); TThread.Queue(nil, procedure var QUserList: TUseList; begin QUserList:= TUseList.Create(True); try MyUserlist.AssignTo(QUserList); FOnUpdateUserList(Self, QUserList); finally QUserList.Free; end; end); Finally MyUserList.Free; end; end; end; |
AW: TThread.Queue - Datenübergabe
Nein, das funktioniert eben nur deswegen mit TStrings, weil die Übergabe von einem Thread auf den anderen mit einem String erfolgt. Durch die implizite Referenzzählung bei Strings erfolgt die Freigabe erst dann, wenn der String nicht mehr gebraucht wird.
Wenn du Objekt-Instanzen übergeben willst, musst du dir über die Ownership klar werden. Du kannst eine Transport-Instanz des Objekts erzeugen und diese dann in der anonymen Methode freigeben:
Delphi-Quellcode:
Es wäre auch interessant zu sehen, was in AssignTo gemacht wird.
procedure TSQLGetUser.DoUpdateUserList(UserList: TUserList);
var MyUserList: TUserList; begin if Assigned(FOnUpdateUserList) then begin MyUserList:=TUserList.Create(True); UserList.AssignTo(MyUserlist); TThread.Queue(nil, procedure begin try FOnUpdateUserList(Self, MyUserList); finally MyUserList.Free; end; end); end; end; Abgesehen davon ist gewinnt dein Beispiel nicht wirklich durch Queue. Da nach dem Synchronize eh nur noch aufgeräumt wird, bringt eine Nebenläufigkeit hier gar nicht so viel. |
AW: TThread.Queue - Datenübergabe
Ah, das mit der implizite Referenzzählung bei Strings wusste ich gar nicht ;-)
Mein AssignTo ist ganz Trivial:
Delphi-Quellcode:
procedure TUser.AssignTo(Dest: TObject);
begin if Dest is TUser then begin (Dest as TUser).guid:=Self.Fguid; (Dest as TUser).Name:=Self.FName; (Dest as TUser).LoggedOn:=Self.FLoggedOn; (Dest as TUser).Calls:=Self.FCalls; (Dest as TUser).Sales:=Self.FSales; end; end; Zitat:
Im Grunde bin ich irgendwo über TThread.Queue gestolpert, und will mir die Funktionweise und Anwendungsmöglichkeiten verdeutlichen bzw. sie dann für mich nutzen. Soweit funktioniert das mit meinen Events alles so wie ich es möchte, aber vielleicht kann ich es ja auch besser machen. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 09: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