Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   MultiThreading (https://www.delphipraxis.net/217091-multithreading.html)

DerMondistunsereSonne 25. Apr 2025 11:19

Delphi-Version: 5

MultiThreading
 
Moin,

Keine Ahnung ob es der richtige Bereich ist, ansonsten einfach verschieben ;)

Also mein Anliegen: Ich möchte gerne beim Laden meines Programmes eine Datei auslesen und das Ergebnis in einer globalen Variablen halten. Der Vorgang dauert ggf. einige Zeit, so dass ich es gerne in einen separten Thread verlagern möchte. Das Ergebnis soll eine TStringList o.ä. sein. Es handelt sich dabei nur um die Zuordnung von einer ID zu einem String.

Desweiteren möchte ich gerne bei einem Aufruf eines Formulars überprüfen, ob es neue Einträge gibt, wenn ja, dann hinzufügen, sonst nichts machen.

Das funktioniert singlethread bereits alles so, wie es soll. Allerdings benötigt der Aufbau der Liste ~5 Sekunden, was zu lang ist.

Und zu guter Letzt: Wenn in dem Form die Liste verwendet wird, soll geschaut werden, ob sie sich noch im Aufbau befindet, dann warten, sonst benutzen.

Alle Klarheiten beseitigt? Gerne ein wenig Beispielcode damit komme ich meist besser zu recht.

Danke!

PS Delphi 12

Edelfix 25. Apr 2025 11:35

AW: MultiThreading
 
Würde ich so eins zu eins in ChatGPT eingeben. "Danke!" könnte man weg lassen.

DerMondistunsereSonne 25. Apr 2025 11:46

AW: MultiThreading
 
Spitzenantwort, ich nutze kein ChatGPT und habe gestern im Radio erfahren, dass Höflichkeiten bei der Verwendung sehr viele Ressourcen binden.

Edelfix 25. Apr 2025 11:58

AW: MultiThreading
 
Kann sein das ich dich falsch verstanden habe. Was genau ist deine Frage?

Uwe Raabe 25. Apr 2025 12:30

AW: MultiThreading
 
Delphi-Quellcode:
unit uMyList;

interface

function MyList: TStringList;

implementation

uses
  System.Threading;

var
  InternalFuture: IFuture<TStringList>;
  InternalList: TStringList;

function MyList: TStringList;
begin
  Result := InternalFuture.Value;
end;

initialization
  InternalList := TStringList.Create;
  InternalFuture := TTask.Future<TStringList>(
    function: TStringList
    begin
      Result := InternalList;
      for var I := 1 to 10000000 do
        Result.Add(I.ToString);
    end);
finalization
  InternalList.Free;
  InternalList := nil;
end;
Der Zugriff im Form auf MyList wartet bei Bedarf auf die Fertigstellung.

DerMondistunsereSonne 25. Apr 2025 12:38

AW: MultiThreading
 
Danke Uwe, ich probiere es mal damit.

@Edelfix Wie man so etwas am geschicktesten angeht.

DerMondistunsereSonne 25. Apr 2025 13:36

AW: MultiThreading
 
Hat funktioniert, vielen Dank!

MyRealName 28. Apr 2025 09:46

AW: MultiThreading
 
Wäre da nicht eher ein TDictionary<Integer,String> besser ?

DerMondistunsereSonne 28. Apr 2025 09:48

AW: MultiThreading
 
Ist es inzwischen auch geworden, hatte mich zwischendurch vertan.

QuickAndDirty 28. Apr 2025 11:51

AW: MultiThreading
 
Ist das nicht ein Problem?
Beim Zugriff auf die MyList aus dem GUI thread heraus, würde schließlich im der GUI Thread angehalten werden(freeze), um auf die Fertigstellung des Futures zu warten.

Uwe Raabe 28. Apr 2025 12:20

AW: MultiThreading
 
Zitat:

Zitat von QuickAndDirty (Beitrag 1548310)
Ist das nicht ein Problem?
Beim Zugriff auf die MyList aus dem GUI thread heraus, würde schließlich im der GUI Thread angehalten werden(freeze), um auf die Fertigstellung des Futures zu warten.

Das ist offenbar so gewollt:
Zitat:

Zitat von DerMondistunsereSonne (Beitrag 1548246)
Und zu guter Letzt: Wenn in dem Form die Liste verwendet wird, soll geschaut werden, ob sie sich noch im Aufbau befindet, dann warten, sonst benutzen.

Mit ein klein wenig mehr Aufwand und Umbau ließe sich das aber auch ohne Freeze realisieren. Ob das bei den genannten 5 Sekunden nötig ist, kann sicher diskutiert werden.

DerMondistunsereSonne 28. Apr 2025 12:24

AW: MultiThreading
 
Ähm ja, ist kein richtiger Freeze, wenn die Liste nicht aufgebaut wäre, würde die Verarbeitung sie aufbauen müssen. Es ist also nur eine Teilaufgabe und die muss immer vollständig abgeschlossen sein, bevor etwas es weiter geht.

@Uwe Hast du etwas Code, damit man den Aufwand ggf. abschätzen kann?

QuickAndDirty 28. Apr 2025 12:33

AW: MultiThreading
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1548311)
Mit ein klein wenig mehr Aufwand und Umbau ließe sich das aber auch ohne Freeze realisieren. Ob das bei den genannten 5 Sekunden nötig ist, kann sicher diskutiert werden.

so hätte ich es gemacht
Delphi-Quellcode:
myform.OnClick(Sender:tObject);
Bgein
  MyGUIListview.enabled := false;
  MYGUiAnyIndicator.visibe := true; // Anzeige Animation im Hauptthread das die Liste aktualisiert wird.
 
  TThread.CreateAnonymousThread(
      procedure
      Begin
        var data:Tarray<TMyItemdataRec> := LoadDataForaShitLoadOfTime; // das dauert.

        //laden fertig ? ok call back in den GUI Thread mit ForceQueue,
        // es kann sein dass der Thread schneller fertig ist als die GUI
        // braucht um die liste disabled neu zu zeichnen und ich mag
        // es in der richtigen Reienfolge wegen BeginUpdate udn EndUpdate
        // daher ForceQueue;
        TThreading.ForceQueue(nil,
          Procedure
          begin
            MyGUIListview.BeginUpdate;
            MyGUIListview.clear;
            For Var i:integer := 0 to high(data) do
              AddTMyListview(data[i]);
            MyGUIListview.Enabled := true;
            MyGUIListview.EndUpdate;
            MYGUiAnyIndicator.visible := false;
          end // procedure
        )//TThreading.ForceQueue(nil,
      end // procedure
  ).Start; // TThread.CreateAnonymousThread(
end;//myform.OnClick(Sender:tObject);

QuickAndDirty 28. Apr 2025 12:43

AW: MultiThreading
 
Und wenn man Feedback und Fehlerbehandlung braucht so
Delphi-Quellcode:
Type
TResultData = Record
  Success:boolean;
  Error:String;
  Data:Tarray<TMyItemdataRec>;
end;

myform.OnClick(Sender:tObject);
Bgein
  MyGUIListview.enabled := false;
  MYGUiAnyIndicator.visibe := true; // Anzeige Animation im Hauptthread das die Liste aktualisiert wird.
 
  TThread.CreateAnonymousThread(
      procedure// ThreadProcedure
      Begin
        var aResult:TResultData := LoadDataForaShitLoadOfTime; // das dauert.
        If aResult.Success then
        Begin

          //laden fertig ? ok call back in den GUI Thread mit ForceQueue,
          // es kann sein dass der Thread schneller fertig ist als die GUI
          // braucht um die liste disabled neu zu zeichnen und ich mag
          // es in der richtigen Reienfolge wegen BeginUpdate udn EndUpdate
          // daher ForceQueue;
          TThreading.ForceQueue(nil,
            Procedure //ForceQueue
            begin
              If aResult.Success then
              Begin
                MyGUIListview.BeginUpdate;
                MyGUIListview.clear;
                For Var i:integer := 0 to length(aResult.data)-1 do
                  AddTMyListview(aResult.data[i]);
                MyGUIListview.Enabled := true;
                MyGUIListview.EndUpdate;
                MYGUiAnyIndicator.visible := false;
              end //If aResult.Success then
              else
              Begin
                Log(' LoadDataForaShitLoadOfTime:'+aResul.error);
                MyGUIListview.Enabled := true;
                MYGUiAnyIndicator.visible := false;            
                ShowMessage(aResul.error);
              end //else If aResult.Success then
            end // procedure //ForceQueue
          )//TThreading.ForceQueue(nil,
      end // procedure// ThreadProcedure
  ).Start; // TThread.CreateAnonymousThread(
end;//myform.OnClick(Sender:tObject);

Uwe Raabe 28. Apr 2025 12:48

AW: MultiThreading
 
Zitat:

Zitat von DerMondistunsereSonne (Beitrag 1548312)
@Uwe Hast du etwas Code, damit man den Aufwand ggf. abschätzen kann?

Klar. Man muss dazu nur das Future publizieren:
Delphi-Quellcode:
unit uMyList;

interface

function MyList: TStringList;
function MyListFuture: IFuture<TStringList>;

implementation

uses
  System.Threading;

var
  InternalFuture: IFuture<TStringList>;
  InternalList: TStringList;

function MyList: TStringList;
begin
  Result := InternalFuture.Value;
end;

function MyListFuture: IFuture<TStringList>;
begin
  Result := InternalFuture;
end;

initialization
  InternalList := TStringList.Create;
  InternalFuture := TTask.Future<TStringList>(
    function: TStringList
    begin
      Result := InternalList;
      for var I := 1 to 10000000 do
        Result.Add(I.ToString);
    end);
finalization
  InternalList.Free;
  InternalList := nil;
end;
Bei der Verwendung prüft man dann den Status des Future auf Completed und reagiert entsprechend.

Alternativ würde es hier auch genügen lediglich den TTaskStatus öffentlich zu machen. Dann muss aber System.Threading eh schon in der uses-Anweisung stehen. Das Future bietet zudem noch die Möglichkeit des Cancel.

Angenommen die Bearbeitung erfolgt auf einen Button-Click. Dann würde ich den Button mit einer TAction mit einem entsprechenden Execute-Event verbinden (sollte übrigens eh schon so sein). Im OnUpdate könnte dann sowas stehen:
Delphi-Quellcode:
begin
  (Sender as TAction).Enabled := (MyListFuture.Status = TTaskStatus.Completed);
end;
Damit lässt sich der Button nur klicken, wenn die Liste fertig ist,

Dort ließe sich auch eine Behandlung für Canceled bzw. Exception unterbringen.

DerMondistunsereSonne 28. Apr 2025 12:48

AW: MultiThreading
 
Danke, das scheint überschaubar. Mal sehen wie weit ich das umsetze.

QuickAndDirty 28. Apr 2025 13:00

AW: MultiThreading
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1548317)
Das Future bietet zudem noch die Möglichkeit des Cancel.

Ernsthaft kann man von außen einen laufenden Thread (oder eben future) beenden????

Also nicht von innen durch abfragen von "Terminated"?

Uwe Raabe 28. Apr 2025 14:48

AW: MultiThreading
 
Zitat:

Zitat von QuickAndDirty (Beitrag 1548319)
Ernsthaft kann man von außen einen laufenden Thread (oder eben future) beenden????

Also nicht von innen durch abfragen von "Terminated"?

Nein, das Cancel setzt auch nur ein Flag. Innerhalb der Future-Routine muss man das schon noch prüfen und entsprechend berücksichtigen. Das Future bietet halt ein einheitliches Interface dafür.

QuickAndDirty 29. Apr 2025 08:59

AW: MultiThreading
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1548323)
Zitat:

Zitat von QuickAndDirty (Beitrag 1548319)
Ernsthaft kann man von außen einen laufenden Thread (oder eben future) beenden????

Also nicht von innen durch abfragen von "Terminated"?

Nein, das Cancel setzt auch nur ein Flag. Innerhalb der Future-Routine muss man das schon noch prüfen und entsprechend berücksichtigen. Das Future bietet halt ein einheitliches Interface dafür.

Vielleicht sollte ich mich mehr mit den Fähigkeiten und best Practices rund um Futures and Tasks beschäftigen...
Mein Problem ist dass Futures bei mir immer die Oberfläche analten...


Ich habe immer nur den GUI Thread und evtl. einen Thread der etwas im Hintergrund tut. Gibt es irgend einen Grund warum ich das lieber in Itask.run statt in einem anonymen Thread machen sollte?

Uwe Raabe 29. Apr 2025 09:43

AW: MultiThreading
 
Zitat:

Zitat von QuickAndDirty (Beitrag 1548340)
Ich habe immer nur den GUI Thread und evtl. einen Thread der etwas im Hintergrund tut. Gibt es irgend einen Grund warum ich das lieber in Itask.run statt in einem anonymen Thread machen sollte?

Am Ende ist es auch nur eine subjektive Entscheidung. Ich persönlich halte viel von einer möglichst engen Kapselung. Das sieht man bei meinem ersten Ansatz, der lediglich die MyList publiziert und die Interna im implementation unterbringt. Im nächsten Schritt wird dann zumindest der Status oder gleich das Future öffentlich gemacht. Das ist halt notwendig, wenn man mehr Kontrolle zulässt.

Es kommt halt immer drauf an was man alles braucht. ITask bzw. IFuture bieten z.B. auch gleich zwei Wait Overloads, mit denen man auch leicht ein non-freezing Poll implementieren kann. Vieles von dem müsste man bei einem simplen anonymen Thread noch selbst beisteuern. Nicht das das nicht auch möglich wäre.

In einem mittlerweile schon über drei Jahre alten Blog Post beschreibe ich den durchaus etwas längeren Weg von Synchron zu Asynchron: Async Tasks in VCL Projects. Dort verwende ich ein simples TTask.Run für die asynchrone Ausführung. Ich finde, es gibt schon genug Dinge zu beachten um Multithreading korrekt zu verwenden; da möchte ich den Code so übersichtlich wie möglich halten.


Alle Zeitangaben in WEZ +1. Es ist jetzt 18:00 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