Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi TThread.Queue - Datenübergabe (https://www.delphipraxis.net/204195-tthread-queue-datenuebergabe.html)

Hobbycoder 5. Mai 2020 12:57

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:
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.
Er soll eine Userliste aus eine Datenbank holen, und mir an meine Hauptthread übergeben.

Der Aufruf erfolgt so:
Delphi-Quellcode:
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;
Wie verpacke ich das jetzt in eine Queue? Ich kann ja der Queue ein Methode übergeben.
Wäre dann Anstelle von
Delphi-Quellcode:
DoUpdateUserList(FUserList);
ein
Delphi-Quellcode:
Queue(nil, frmMOMain.ThreadUpdateUserlist(FUserList));
korrekt und ich könnte dann den Event "TOnUpdateUserList" weglassen?

himitsu 5. Mai 2020 13:20

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:
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;
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.

Hobbycoder 5. Mai 2020 13:42

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.

Uwe Raabe 5. Mai 2020 13:54

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;

Hobbycoder 5. Mai 2020 14:09

AW: TThread.Queue - Datenübergabe
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1463715)
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;

Okay, nun sind meine benötigten Daten doch etwas umfangreicher als eine StringList. Aber was mit einer StringList geht, geht auch mit einer TObjectList;

Also müsste das ja äquivalent funktionieren:
Delphi-Quellcode:
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;
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.

Uwe Raabe 5. Mai 2020 14:36

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:
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;
Es wäre auch interessant zu sehen, was in AssignTo gemacht wird.

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.

Hobbycoder 5. Mai 2020 14:47

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:

Zitat von Uwe Raabe (Beitrag 1463732)
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.

Es gibt natürlich auch noch Threads, die nicht nur simple Dinge tun, eine längere Laufzeit und auch eine längere Lebensdauer haben. Wo Daten auch mal zwischendurch übergeben werden.
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 14:54 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