AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

TThread.Queue - Datenübergabe

Ein Thema von Hobbycoder · begonnen am 5. Mai 2020 · letzter Beitrag vom 5. Mai 2020
Antwort Antwort
Hobbycoder

Registriert seit: 22. Feb 2017
961 Beiträge
 
#1

TThread.Queue - Datenübergabe

  Alt 5. Mai 2020, 13:57
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 DoUpdateUserList(FUserList); ein Queue(nil, frmMOMain.ThreadUpdateUserlist(FUserList)); korrekt und ich könnte dann den Event "TOnUpdateUserList" weglassen?
Gruß Hobbycoder
Alle sagten: "Das geht nicht.". Dann kam einer, der wusste das nicht, und hat's einfach gemacht.
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.184 Beiträge
 
Delphi 12 Athens
 
#2

AW: TThread.Queue - Datenübergabe

  Alt 5. Mai 2020, 14:20
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

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.
$2B or not $2B

Geändert von himitsu ( 5. Mai 2020 um 14:22 Uhr)
  Mit Zitat antworten Zitat
Hobbycoder

Registriert seit: 22. Feb 2017
961 Beiträge
 
#3

AW: TThread.Queue - Datenübergabe

  Alt 5. Mai 2020, 14:42
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.
Gruß Hobbycoder
Alle sagten: "Das geht nicht.". Dann kam einer, der wusste das nicht, und hat's einfach gemacht.
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.475 Beiträge
 
Delphi 12 Athens
 
#4

AW: TThread.Queue - Datenübergabe

  Alt 5. Mai 2020, 14:54
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;
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
Hobbycoder

Registriert seit: 22. Feb 2017
961 Beiträge
 
#5

AW: TThread.Queue - Datenübergabe

  Alt 5. Mai 2020, 15:09
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.
Gruß Hobbycoder
Alle sagten: "Das geht nicht.". Dann kam einer, der wusste das nicht, und hat's einfach gemacht.
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.475 Beiträge
 
Delphi 12 Athens
 
#6

AW: TThread.Queue - Datenübergabe

  Alt 5. Mai 2020, 15:36
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.
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
Hobbycoder

Registriert seit: 22. Feb 2017
961 Beiträge
 
#7

AW: TThread.Queue - Datenübergabe

  Alt 5. Mai 2020, 15:47
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;
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.
Gruß Hobbycoder
Alle sagten: "Das geht nicht.". Dann kam einer, der wusste das nicht, und hat's einfach gemacht.
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 01:26 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