Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Erste Schritte Multi-Threading (https://www.delphipraxis.net/213869-erste-schritte-multi-threading.html)

Ykcim 10. Okt 2023 22:43

Erste Schritte Multi-Threading
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo Zusammen,
ich habe eine Client/Server Applikation, in welcher die Server-App einige aufwändige Abfragen auf unterschiedlichen Datenbanken durchführt.
Diese Abfragen dauern ein paar Sekunden und wenn es unglücklich läuft, blockiert eine Abfrage eine andere. Das gibt dann eine Exception, die zwar abfgefangen werden, aber ich möchte das Ganze gerne in den Griff bekommen.

Daher wage ich mich erstmalig an das Thema Multi-Threading heran.

Ich habe eine ganz banale App geschrieben, in der ich die ersten Versuche bestritte habe und ich würde gerne mal ein Feedback von Euch hören, für die das Routine ist.

Delphi-Quellcode:
unit Frm_Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    lbl_eins: TLabel;
    lbl_zwei: TLabel;
    btn_eins: TButton;
    btn_zwei: TButton;
    lbl_Zeit: TLabel;
    Timer1: TTimer;
    Timer_Eins_Start: TButton;
    Timer_Zwei_Start: TButton;
    Timer_Eins_Pause: TButton;
    Timer_Zwei_Pause: TButton;
    Timer_Eins_Resume: TButton;
    Timer_Zwei_Resume: TButton;
    Timer_Eins_Stop: TButton;
    Timer_Zwei_Stop: TButton;
    btn_EinsZwei: TButton;
    procedure btn_einsClick(Sender: TObject);
    procedure btn_zweiClick(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure Timer_Eins_StartClick(Sender: TObject);
    procedure Timer_Eins_ResumeClick(Sender: TObject);
    procedure Timer_Eins_StopClick(Sender: TObject);
    procedure Timer_Zwei_StartClick(Sender: TObject);
    procedure Timer_Eins_PauseClick(Sender: TObject);
    procedure Timer_Zwei_PauseClick(Sender: TObject);
    procedure Timer_Zwei_ResumeClick(Sender: TObject);
    procedure Timer_Zwei_StopClick(Sender: TObject);
    procedure btn_EinsZweiClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
    //Alles im Main-Thread
    procedure Timer_Eins;
    procedure Timer_Zwei;
    //Wird vom MyThread aufgerufen
    procedure Write_Counter_Eins(Counter_Eins: integer);
    procedure Write_Counter_Zwei (Counter_Zwei: integer);
    procedure Write_Uhr (Zeit: TTime);
  end;

TMyThread_Eins = class(TThread)
   public
      procedure Execute; override;
end;

TMyThread_Zwei = class(TThread)
   public
      procedure Execute; override;
end;

TMyThread_Uhr = class(TThread)
   public
      procedure Execute; override;
end;

var
  Form1: TForm1;
  TH_Eins: TMyThread_Eins;
  TH_Zwei: TMyThread_Zwei;
  TH_Uhr : TMyThread_Uhr;

implementation

{$R *.dfm}

procedure TForm1.btn_einsClick(Sender: TObject);
begin
   Timer_Eins;
end;

procedure TForm1.btn_zweiClick(Sender: TObject);
begin
   Timer_Zwei;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
   //TH_Eins.Free;
   //TH_Zwei.Free;
   TH_Uhr.Free;
end;

procedure TForm1.FormShow(Sender: TObject);
begin
   TH_Uhr := TMyThread_Uhr.Create(False);
end;

procedure TForm1.btn_EinsZweiClick(Sender: TObject);
begin
   TH_Eins := TMyThread_Eins.Create(False);
   TH_Zwei := TMyThread_Zwei.Create(False);
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
   lbl_Zeit.Caption := TimeToStr(now());
end;

procedure TForm1.Timer_Eins;
var  I: integer;
      sEins: integer;
      c_Eins: integer;
begin
   sEins := 1000;
   c_Eins := 0;
   lbl_eins.Caption := IntToStr(c_Eins);
   lbl_eins.Refresh;
   for I := 0 to 9 do begin
      sleep(sEins);
      INC(c_Eins);
      lbl_eins.Caption := IntToStr(c_Eins);
      lbl_eins.Refresh;
   end;
end;

procedure TForm1.Timer_Zwei;
var  I: integer;
      sZwei: integer;
      c_Zwei: integer;
begin
   sZwei := 1000;
   c_Zwei := 0;
   lbl_zwei.Caption := IntToStr(c_Zwei);
   lbl_zwei.Refresh;
   for I := 0 to 9 do begin
      sleep(sZwei);
      INC(c_Zwei);
      lbl_zwei.Caption := IntToStr(c_Zwei);
      lbl_zwei.Refresh;
   end;
end;

procedure TForm1.Timer_Eins_PauseClick(Sender: TObject);
begin
   if not TH_Eins.Terminated then begin
      TH_Eins.Suspend;
   end;
end;

procedure TForm1.Timer_Eins_ResumeClick(Sender: TObject);
begin
   if not TH_Eins.Terminated then begin
      TH_Eins.Resume;
   end;
end;

procedure TForm1.Timer_Eins_StartClick(Sender: TObject);
begin
   TH_Eins := TMyThread_Eins.Create(False);
end;

procedure TForm1.Timer_Eins_StopClick(Sender: TObject);
begin
   TH_Eins.Terminate;
end;

procedure TForm1.Timer_Zwei_PauseClick(Sender: TObject);
begin
   if not TH_Zwei.Terminated then begin
      TH_Zwei.Suspend;
   end;
end;

procedure TForm1.Timer_Zwei_ResumeClick(Sender: TObject);
begin
   if not TH_Zwei.Terminated then begin
      TH_Zwei.Resume;
   end;
end;

procedure TForm1.Timer_Zwei_StartClick(Sender: TObject);
begin
   TH_Zwei := TMyThread_Zwei.Create(False);
end;

procedure TForm1.Timer_Zwei_StopClick(Sender: TObject);
begin
   TH_Zwei.Terminate;
end;

procedure TForm1.Write_Counter_Eins(Counter_Eins: integer);
begin
   lbl_eins.Caption := IntToStr(Counter_Eins);
   lbl_eins.Refresh;
end;

procedure TForm1.Write_Counter_Zwei(Counter_Zwei: integer);
begin
   lbl_zwei.Caption := IntToStr(Counter_Zwei);
   lbl_zwei.Refresh;
end;

procedure TForm1.Write_Uhr(Zeit: TTime);
begin
   lbl_Zeit.Caption := TimeToStr(Zeit);
   lbl_Zeit.Refresh;
end;

{ TMyTread_Eins }

procedure TMyThread_Eins.Execute;
var  I: integer;
      sEins: integer;
      c_Eins: integer;
begin
   sEins := 1000;
   c_Eins := 0;
   Synchronize(procedure
               begin
                  Form1.Write_Counter_Eins(c_Eins);
               end);
   for I := 0 to 19 do begin
      if Terminated then begin
         TH_Eins.Free;
         Break;
      end;
      sleep(sEins);
      INC(c_Eins);
      if Terminated then begin
         TH_Eins.Free;
         Break;
      end;
      Synchronize(procedure
                  begin
                     Form1.Write_Counter_Eins(c_Eins);
                  end);
   end;
end;

{ TMyThread_Zwei }

procedure TMyThread_Zwei.Execute;
var  I: integer;
      sZwei: integer;
      c_Zwei: integer;
begin
   sZwei := 1000;
   c_Zwei := 0;
   Synchronize(procedure
               begin
                  Form1.Write_Counter_Zwei(c_Zwei);
               end);
   for I := 0 to 19 do begin
      if Terminated then begin
         TH_Zwei.Free;
         Break;
      end;
      sleep(sZwei);
      INC(c_Zwei);
      if Terminated then begin
         TH_Zwei.Free;
         Break;
      end;
      Synchronize(procedure
               begin
                  Form1.Write_Counter_Zwei(c_Zwei);
               end);
   end;
end;

{ TMyThread_Uhr }

procedure TMyThread_Uhr.Execute;
var  I: integer;
      sUhr: integer;
begin
   sUhr := 1000;
   while not Terminated do begin
      sleep(sUhr);
      Synchronize(procedure
                  begin
                     Form1.Write_Uhr(Now());
                  end);
   end;

end;

end.
Dann habe ich auch noch zwei konkrete Frage: Wenn ich einen der beiden Threads durch erneutes Klicken des "Eins_Start" starte, während der Thread noch läuft, dass dann quasi ein weiterer Thread gestartet wird. Erkennbar, dass die Ausgabe zwischen den beiden Threads wechselt. Wie ist das möglich? Was für ein Thread wird denn da erzeugt? Ich habe doch mit einer globalen Variable gearbeitet?
Die zweite Frage bezieht sich auf Threads, die zur Laufzeit erzeugt werden: Wie kann ich einen Thread ansprechen, der zur Laufzeit erzeugt wurde? Ich habe das in dem kleinen Programm gelöst, indem ich mit globalen Variablen glöst habe. Aber ich habe keine Lösung gefunden, wie ich einen Thread, der zur Laufzeit ansprechen und z.B. pausieren oder stoppen kann.

Vielen Dank
Patrick

haentschman 11. Okt 2023 06:26

AW: Erste Schritte Multi-Threading
 
Liste der Anhänge anzeigen (Anzahl: 1)
Moin...8-)

Ich fange mal an...
Zitat:

Ich habe doch mit einer globalen Variable gearbeitet
...genau deshalb (u.a.) sind die globale Variablen so verpönt. :wink:
Zitat:

Wie ist das möglich? Was für ein Thread wird denn da erzeugt?
Du erzeugst eine Thread und legst ihn in die Variable ab. Das ist alles.
ABER: Du tauscht nur den Pointer auf den Thread IN der Variable aus.
Sinnbild:
1. Tasse mit Teebeutel = Thread in der Variable
2. neuer Teebeutel soll in die Tasse = Erzeugung Thread
3. Teebeutel in der Tasse wird auf den Tisch gelegt und der neue Teebeutel kommt in die Tasse.
-> beide Teebeutel existieren. :wink: Die Tasse kennt aber nur den Teebeutel in der Tasse.
Zitat:

Wie kann ich einen Thread ansprechen, der zur Laufzeit erzeugt wurde?
Das betrifft auch den obigen Teil. Jeder Thread ist quasi nur ein Pointer in der Variable.
Die Lösung: Verwalte deine Threads in Listen oder Dictionaries (je nach Anwendungsfall). Dann kannst du dir den Thread aus der Liste wieder in eine Variable "laden" und kannst dann damit arbeiten. ...fertsch. :wink:
https://docwiki.embarcadero.com/Libr...es.TThreadList

Delphi-Quellcode:
Synchronize(procedure
                  begin
                     Form1.Write_Uhr(Now());
                  end);
Synchronize ist ja soweit ok.
Für die Zukunft aber:
Die Threads sollten eine eigene Unit haben. Der Thread und die Thread Unit, dürfen die Anwesenheit der Form NICHT kennen. Dem Thread muß egal sein was dann passiert.
Thema Wiederverwendbarkeit und zirkuläre Aufrufe.
Lösung:
Im Synchronize mit Events Arbeiten. Das aufrufende Objekt, in deinem Falle die Form, nimmt das Event entgegen und macht in ihrem "Bereich" das was zu zun ist: Write_Uhr(Now)
Beispiel:
Delphi-Quellcode:
Synchronize(procedure
                  begin
                     if Assigned(FOnChangeTime) then
                     begin
                       FOnChangeTime(Self);
                     end;
                  end);
PS: :zwinker:
Bitte vermeide Denglisch. Das tut an den Augen weh.
Delphi-Quellcode:
TH_Eins: TMyThread_Eins;
besser
Delphi-Quellcode:
THOne: TMyThreadOne;
...
Bitte keine Unterstriche sondern CamelCase https://de.wikipedia.org/wiki/Binnen...ammiersprachen
Bitte gebe deinen Componenten, auch zum Testen, von Anfang an sprechende Namen. Form1 ist :kotz:

himitsu 11. Okt 2023 09:32

AW: Erste Schritte Multi-Threading
 
Das mit dem Teebeutel geht auch anders auszugrücken.

Neuen Thread erzeugen und in die Tasse hängen


Teebeutel holen (Thread erstellen)
Teebeutel in Tasse hängen + Schnurr um den Hänkel wickeln (Zeiger der Variable zuweisen)
nochmal einen Teebeutel holen (Thread erstellen)
Teebeutel in Tasse hängen + Schnurr um den Hänkel wickeln (Zeiger der Variable zuweisen)
-> alter Zeiger hat in Variable keinen Platz und wird überschrieben = Schurr abschneiden und neue Schnurr drumwickeln -> Beutel bleibt aber in der Tasse (du beendest ja den alten Thread nicht)

Kas Ob. 11. Okt 2023 10:45

AW: Erste Schritte Multi-Threading
 
Cup, Tea and bag.... ?!!!! :?
Leave that stuff to the English, replace them with cars and car equipment's, though i remember Uwe love car examples, or wait... may be he did hate them :stupid: :duck: ,
can't remember now, all i have that there is some emotional connection between Uwe and cars.

Ykcim 23. Nov 2023 15:24

AW: Erste Schritte Multi-Threading
 
Hallo Zusammen,

vielen Dank für Eure Erklärungen und Hinweise! Ich habe versucht diese umzusetzen (außer DENGLISCH, das habe ich jetzt nicht angefasst ;-)), bin aber trotz intensiver Suche an ziemlich allen Fronten gescheitert.

Ich habe dann versucht schrittweise vorzugehen und die Threads erst einmal in eine eigene Unit auszulagern.
Weil es mir noch nicht gelungen ist, ein Event zu definieren, habe ich die Form in die uses gepackt.

Die TThreadUnit

Delphi-Quellcode:
unit TMyThreadUnit;

interface

uses Windows,
      Messages,
      SysUtils,
      Classes,
      Graphics,
      Controls,
      Forms;

type



TMyThreads = class
      strict protected

      private

      public
         constructor Create;
end;


TMyThread_Eins = class(TThread)
   public
      procedure Execute; override;
end;

TMyThread_Zwei = class(TThread)
   public
      procedure Execute; override;
end;

TMyThread_Uhr = class(TThread)
   public
      procedure Execute; override;
end;

var  MyThreads: TMyThreads;
      TH_Eins: TMyThread_Eins;
      TH_Zwei: TMyThread_Zwei;
      TH_Uhr : TMyThread_Uhr;


{ TMyThreads }

implementation

uses Frm_Main;

{ TMyThreads }

constructor TMyThreads.Create;
begin

end;

{ TMyTread_Eins }

procedure TMyThread_Eins.Execute;
var  I: integer;
      sEins: integer;
      c_Eins: integer;
begin
   sEins := 1000;
   c_Eins := 0;
   Synchronize(procedure
               begin
                  Form1.Write_Counter_Eins(c_Eins);
               end);
   for I := 0 to 19 do begin
      if Terminated then begin
         TH_Eins.Free;
         Break;
      end;
      sleep(sEins);
      INC(c_Eins);
      if Terminated then begin
         TH_Eins.Free;
         Break;
      end;
      Synchronize(procedure
                  begin
                     Form1.Write_Counter_Eins(c_Eins);
                  end);
   end;
end;

{ TMyThread_Zwei }

procedure TMyThread_Zwei.Execute;
var  I: integer;
      sZwei: integer;
      c_Zwei: integer;
begin
   sZwei := 1000;
   c_Zwei := 0;
   Synchronize(procedure
               begin
                  Form1.Write_Counter_Zwei(c_Zwei);
               end);
   for I := 0 to 19 do begin
      if Terminated then begin
         TH_Zwei.Free;
         Break;
      end;
      sleep(sZwei);
      INC(c_Zwei);
      if Terminated then begin
         TH_Zwei.Free;
         Break;
      end;
      Synchronize(procedure
               begin
                  Form1.Write_Counter_Zwei(c_Zwei);
               end);
   end;
end;

{ TMyThread_Uhr }

procedure TMyThread_Uhr.Execute;
var  I: integer;
      sUhr: integer;
begin
   sUhr := 1000;
   while not Terminated do begin
      sleep(sUhr);
      Synchronize(procedure
                  begin
                     Form1.Write_Uhr(Now());
                  end);
   end;

end;



end.
Und dann die Aufrufe in der Form:

Delphi-Quellcode:
unit Frm_Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, TMyThreadUnit;

type
   TForm1 = class(TForm)
      lbl_eins: TLabel;
      lbl_zwei: TLabel;
      btn_eins: TButton;
      btn_zwei: TButton;
      lbl_Zeit: TLabel;
      Timer1: TTimer;
      Timer_Eins_Start: TButton;
      Timer_Zwei_Start: TButton;
      Timer_Eins_Pause: TButton;
      Timer_Zwei_Pause: TButton;
      Timer_Eins_Resume: TButton;
      Timer_Zwei_Resume: TButton;
      Timer_Eins_Stop: TButton;
      Timer_Zwei_Stop: TButton;
      btn_EinsZwei: TButton;
      procedure btn_einsClick(Sender: TObject);
      procedure btn_zweiClick(Sender: TObject);
      procedure Timer1Timer(Sender: TObject);
      procedure Timer_Eins_StartClick(Sender: TObject);
      procedure Timer_Eins_ResumeClick(Sender: TObject);
      procedure Timer_Eins_StopClick(Sender: TObject);
      procedure Timer_Zwei_StartClick(Sender: TObject);
      procedure Timer_Eins_PauseClick(Sender: TObject);
      procedure Timer_Zwei_PauseClick(Sender: TObject);
      procedure Timer_Zwei_ResumeClick(Sender: TObject);
      procedure Timer_Zwei_StopClick(Sender: TObject);
      procedure btn_EinsZweiClick(Sender: TObject);
      procedure FormShow(Sender: TObject);
      procedure FormClose(Sender: TObject; var Action: TCloseAction);
   private
      { Private-Deklarationen }
   public
      { Public-Deklarationen }
      //Alles im Main-Thread
      procedure Timer_Eins;
      procedure Timer_Zwei;
      //Wird vom MyThread aufgerufen
      procedure Write_Counter_Eins(Counter_Eins: integer);
      procedure Write_Counter_Zwei (Counter_Zwei: integer);
      procedure Write_Uhr (Zeit: TTime);

   end;



var
  Form1: TForm1;


implementation
//uses TMyThreadUnit;

{$R *.dfm}


procedure TForm1.btn_einsClick(Sender: TObject);
begin
   Timer_Eins;
end;

procedure TForm1.btn_zweiClick(Sender: TObject);
begin
   Timer_Zwei;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
   TH_Uhr.Free;
end;

procedure TForm1.FormShow(Sender: TObject);
begin
   TH_Uhr := TMyThread_Uhr.Create(False);
end;

procedure TForm1.btn_EinsZweiClick(Sender: TObject);
begin
   TH_Eins := TMyThread_Eins.Create(False);
   TH_Zwei := TMyThread_Zwei.Create(False);
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
   lbl_Zeit.Caption := TimeToStr(now());
end;

procedure TForm1.Timer_Eins;
var  I: integer;
      sEins: integer;
      c_Eins: integer;
begin
   sEins := 1000;
   c_Eins := 0;
   lbl_eins.Caption := IntToStr(c_Eins);
   lbl_eins.Refresh;
   for I := 0 to 9 do begin
      sleep(sEins);
      INC(c_Eins);
      lbl_eins.Caption := IntToStr(c_Eins);
      lbl_eins.Refresh;
   end;
end;

procedure TForm1.Timer_Zwei;
var  I: integer;
      sZwei: integer;
      c_Zwei: integer;
begin
   sZwei := 1000;
   c_Zwei := 0;
   lbl_zwei.Caption := IntToStr(c_Zwei);
   lbl_zwei.Refresh;
   for I := 0 to 9 do begin
      sleep(sZwei);
      INC(c_Zwei);
      lbl_zwei.Caption := IntToStr(c_Zwei);
      lbl_zwei.Refresh;
   end;
end;

procedure TForm1.Timer_Eins_PauseClick(Sender: TObject);
begin
   if not TH_Eins.Terminated then begin   //Auf protected Symbol TThread.Terminated kann nicht zugegriffen werden
      TH_Eins.Suspend;
   end;
end;

procedure TForm1.Timer_Eins_ResumeClick(Sender: TObject);
begin
   if not TH_Eins.Terminated then begin   //Auf protected Symbol TThread.Terminated kann nicht zugegriffen werden
     TMyThreadUnit.TH_Eins.Resume;
   end;
end;

procedure TForm1.Timer_Eins_StartClick(Sender: TObject);
begin
   TH_Eins := TMyThread_Eins.Create(False);
end;

procedure TForm1.Timer_Eins_StopClick(Sender: TObject);
begin
   TH_Eins.Terminate;
end;

procedure TForm1.Timer_Zwei_PauseClick(Sender: TObject);
begin
   if not TH_Zwei.Terminated then begin   //Auf protected Symbol TThread.Terminated kann nicht zugegriffen werden
      TH_Zwei.Suspend;
   end;
end;

procedure TForm1.Timer_Zwei_ResumeClick(Sender: TObject);
begin
   if not TH_Zwei.Terminated then begin   //Auf protected Symbol TThread.Terminated kann nicht zugegriffen werden
      TH_Zwei.Resume;
   end;
end;

procedure TForm1.Timer_Zwei_StartClick(Sender: TObject);
begin
   TH_Zwei := TMyThread_Zwei.Create(False);
end;

procedure TForm1.Timer_Zwei_StopClick(Sender: TObject);
begin
   TH_Zwei.Terminate;
end;

procedure TForm1.Write_Counter_Eins(Counter_Eins: integer);
begin
   lbl_eins.Caption := IntToStr(Counter_Eins);
   lbl_eins.Refresh;
end;

procedure TForm1.Write_Counter_Zwei(Counter_Zwei: integer);
begin
   lbl_zwei.Caption := IntToStr(Counter_Zwei);
   lbl_zwei.Refresh;
end;

procedure TForm1.Write_Uhr(Zeit: TTime);
begin
   lbl_Zeit.Caption := TimeToStr(Zeit);
   lbl_Zeit.Refresh;
end;

end.
Aber ich mache anscheinend grundlegende Fehler in dem Aufbau der ThreadUnit. Ich kann das Programm so nicht kompilieren, weil TH_Eins.Terminated von der Form aus nicht geprüft werden kann.

Daher meine Fragen:
1. Wie muss ich die ThreadUnit aufbauen? Ich muss ja für jeden Thread einen eigenen Typen definieren...
2. Hat jemand vielleicht ein einfaches Beispiel, wie ich das mit dem Event lösen muss?

Vielen Dank
Patrick

Sherlock 23. Nov 2023 15:37

AW: Erste Schritte Multi-Threading
 
Zu Events hilft eventuell als Einstieg die...Hilfe: http://docwiki.embarcadero.com/RADSt...gnisse_(Delphi)

Ykcim 27. Nov 2023 13:09

AW: Erste Schritte Multi-Threading
 
Hallo Sherlock,

vielen Dank für den Link. Leider läuft der ins Leere... Hast Du noch einen funktionierenden?

Vielen Dank
Patrick

dummzeuch 27. Nov 2023 13:10

AW: Erste Schritte Multi-Threading
 
Zitat:

Zitat von Ykcim (Beitrag 1530121)
vielen Dank für den Link. Leider läuft der ins Leere... Hast Du noch einen funktionierenden?

Da fehlte ein ")" am Ende:
http://docwiki.embarcadero.com/RADSt...gnisse_(Delphi)

Ykcim 27. Nov 2023 13:32

AW: Erste Schritte Multi-Threading
 
Vielen Dank!

Jetzt funktioniert der Link!

Kann mir noch jemand einen Tip geben, wie ich die Thread-Unit aufbauen sollte? Da steckt noch der Wurm drin...

LG Patrick

himitsu 27. Nov 2023 13:40

AW: Erste Schritte Multi-Threading
 
Zitat:

Zitat von dummzeuch (Beitrag 1530122)

Bei dir auch.
[URL] selbst drumrumschreiben, oder den Link-Button benutzen.

Leider sind automatische Links zu doof, einfach die ) mitzunehmen, wenn im Link eine ( drin ist.

Edelfix 28. Nov 2023 07:15

AW: Erste Schritte Multi-Threading
 
Wenn du im Projekt eine neue Unit hinzufügst dann hast du die Möglichkeit eine neue Unit von Type Thread zu wählen. Dann ist die Unit vor formatiert.

Ykcim 28. Nov 2023 09:14

AW: Erste Schritte Multi-Threading
 
Vielen Dank, dass wusste ich noch nicht.
Ich habe die Auslagerung gestern noch mit einer eigenen Unit gelöst, werde ich nachher posten.

Kurze Frage zu den Events: Sollten diese auch in einer eigenen Unit definiert werden oder im der Form, in der sie zur Anwendung kommen sollen?

Ich habe mich an den folgenden Links entlanggehangelt:
https://docwiki.embarcadero.com/RADS...nisse_(Delphi)

und bei diesem Thema an Post 2
https://www.delphipraxis.net/70116-e...rammieren.html

Das ist bis jetzt dabei rausgekommen:
Delphi-Quellcode:
type
   TTimeEvent = procedure (TimeValue: TTime) of object;
   TTimeObject = class
   private
     FTimeValue: TTimeEvent;

   public
     property OnGetTime: TTimeEvent read FTimeValue write FTimeValue;
     { Löst das Ereignis aus, wenn etwas registriert ist }
     procedure TriggerTimeEvent(TimeValue: TTime);
   end;
   TForm1 = class(TForm)
      lbl_eins: TLabel;
      lbl_zwei: TLabel;

...

private
      { Private-Deklarationen }
      MyThread: TMyThreads;

...

var
  Form1: TForm1;
  TimeObject: TTimeObject;

implementation

{$R *.dfm}

{ TTimeObject }

procedure TTimeObject.TriggerTimeEvent(TimeValue: TTime);
begin
   if Assigned(FTimeValue) then begin
      FTimeValue(TimeValue);
   end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
   MyThread := TMyThreads.Create;
   TimeObject := TTimeObject.Create;
   TimeObject.OnGetTime := Write_Uhr;
end;

procedure TForm1.FormShow(Sender: TObject);
begin
   MyThread.TH_Uhr_Start(False);
end;
In meiner ThreadUnit muss ich jetzt aber irgendwie den Aufruf anstoßen:
Delphi-Quellcode:
{ TMyThread_Uhr }

procedure TMyThread_Uhr.Execute;
var  I: integer;
      sUhr: integer;
begin
   sUhr := 1000;
   while not Terminated do begin
      sleep(sUhr);
      Synchronize(procedure
                  begin
                     Form1.Write_Uhr(Now());
                  end);
   end;

end;
Und da weiß ich nicht, wie ich den Bezug zu Form1 loswerde...

Vielen Dank
Patrick

haentschman 28. Nov 2023 10:31

AW: Erste Schritte Multi-Threading
 
Zitat:

Und da weiß ich nicht, wie ich den Bezug zu Form1 loswerde
...Event? :gruebel: https://www.delphipraxis.net/1527953-post2.html

himitsu 28. Nov 2023 10:37

AW: Erste Schritte Multi-Threading
 
Als Parameter dem Contructor (oder anschließend einen Property/Setter) mitgeben
und intern speichern (Feld/Variable in der ThreadKlasse).

aber Event/Callback ist besser (unabhängiger).



Oder statt der Form-Instanz ein Interface deklarieren, in der Form implementieren, dieses als Parameter übergeben und intern speichern.
Schon kann man auch bei Anderem dieses Interface implementieren und diese Threadklasse, ebenso wie beim Event/Callback, auch für Anderes wiederverwenden, da es nicht direkt mit "dieser" Form verknubbelt ist.

In TComponent ist ein IInterface als Basis implementiert, ohne Referenzzählung, damit es mit der Speicherverwaltung der VCL nicht kollidiert. (sowie auch FMX usw.)

Edelfix 28. Nov 2023 10:58

AW: Erste Schritte Multi-Threading
 
Bei Events und Threads tuhe ich mich auch noch schwer.

Das Problem ist das der Thread die Form nicht kennen soll aber das Event der Form und dem Thread bekannt sein soll.

Ich habe mir mit einer dritten Unit beholfen die beiden bekannt ist. In der habe ich das Event angelegt.

Ich glaube es gibt eine Lösing mit Pointer und dem @ Zeichen und eine schwer verständliche Interface Lösung.

himitsu 28. Nov 2023 11:03

AW: Erste Schritte Multi-Threading
 
z.B. dort, wo der Thread dekaliert ist, oder in einer gemeinsam genutzten Unit wird eine Event-Signatur deklariert
Delphi-Quellcode:
type TMyEvent = procedure(.....) of object;

Das als Parameter, Setter-Funktion oder Property in die ThreadKlasse.
Und schon kann die Form eine "gleich" aufgebaute Objekt-Methode an das übergeben.

Entsprechend dem
Delphi-Quellcode:
OnGetTime := Write_Uhr

und
Delphi-Quellcode:
   if Assigned(FTimeValue) then
      FTimeValue(TimeValue);

Edelfix 28. Nov 2023 11:07

AW: Erste Schritte Multi-Threading
 
Mit hat folgender Link sehr geholfen (obwohl ich nicht alles verstanden / umgesetzt habe):
https://www.uweraabe.de/Blog/2021/11...-vcl-projects/

Ykcim 28. Nov 2023 14:38

AW: Erste Schritte Multi-Threading
 
Hallo Zusammen,

das ganze Thema ist für mich dünnes Eis, daher habe ich mich seit Wochen schwergetan, es umzusetzen. Diese Lösung scheint zu arbeiten, aber möglicherweise sagt Ihr mir gleich, wo ich "einbrechen" werde...
Ich habe es jetzt ohne Events und Callbacks realisiert und der Sync-Procedure eine Procedure aus dem MainForm gegeben...

In der Thread-Unit habe ich einen Typen definiert und der Threadklasse TMyThread_Uhr eine Variable und eine Property:
Delphi-Quellcode:
type

   TWriteClockValue = procedure (TimeValue: TTime) of Object;

   TMyThread_Uhr = class(TThread)
      private
         fWriteClockValue: TWriteClockValue;
      public
         procedure Execute; override;
         property WriteClockValue: TWriteClockValue read fWriteClockValue write fWriteClockValue;
   end;
Die Synchronisierung habe ich dann so vorgenommen:
Delphi-Quellcode:
procedure TMyThread_Uhr.Execute;
var  I: integer;
      sUhr: integer;
      O: TObject;
      T: TTime;
begin
   sUhr := 1000;
   while not Terminated do begin
      sleep(sUhr);
      Synchronize(procedure
                  begin
                     if Assigned(fWriteClockValue) then
                        fWriteClockValue(Now());
                  end);
   end;
end;
In der aufrufenden Form sieht das dann so aus:
Delphi-Quellcode:
procedure TfrmMain.FormShow(Sender: TObject);
begin
   MyThread.TH_Uhr_Start(True);
   MyThread.TH_Uhr.WriteClockValue := Write_Uhr;
   MyThread.TH_Uhr.Resume;
end;
Die Procedure Write_Uhr ist ein dem Formular so definiert:
Delphi-Quellcode:
procedure TfrmMain.Write_Uhr(Zeit: TTime);
begin
   lbl_Zeit.Caption := TimeToStr(Zeit);
   lbl_Zeit.Refresh;
end;

Ich erstelle den Thread, pausiere ihn, weise die Procedure Write_Uhr aus der Form der Variablen in dem Thread zu und lasse den Thread dann laufen.

Das scheint so zu funktionieren. Ist das eine anständige Lösung?

Hier der gesamte Code der kleinen Test-App

Meine Thread-Unit
Delphi-Quellcode:
unit TMyThreadUnit;

interface

uses Windows, Messages, SysUtils, Classes;

type

   TWriteClockValue = procedure (TimeValue: TTime) of Object;
   TWriteTHEinsValue = procedure (THValue: integer) of Object;
   TWriteTHZweiValue = procedure (THValue: integer) of Object;

   TMyThread_Eins = class(TThread)
      private
         fWriteTHEinsValue: TWriteTHEinsValue;
      public
         procedure Execute; override;
         property WriteTHEinsValue: TWriteTHEinsValue read fWriteTHEinsValue write fWriteTHEinsValue;
   end;

   TMyThread_Zwei = class(TThread)
      private
         fWriteTHZweiValue: TWriteTHZweiValue;
      public
         procedure Execute; override;
         property WriteTHZweiValue: TWriteTHZweiValue read fWriteTHZweiValue write fWriteTHZweiValue;
   end;

   TMyThread_Uhr = class(TThread)
      private
         fWriteClockValue: TWriteClockValue;
      public
         procedure Execute; override;
         property WriteClockValue: TWriteClockValue read fWriteClockValue write fWriteClockValue;
   end;

   TMyThreads = class
      strict protected

      private
         fTH_Eins: TMyThread_Eins;
         fTH_Zwei: TMyThread_Zwei;
         fTH_Uhr : TMyThread_Uhr;

      public
         constructor Create;
         property TH_Eins: TMyThread_Eins read fTH_Eins write fTH_Eins;
         property TH_Zwei: TMyThread_Zwei read fTH_Zwei write fTH_Zwei;
         property TH_Uhr : TMyThread_Uhr read fTH_Uhr write fTH_Uhr;
         procedure TH_Eins_Start(breaked: boolean);
         procedure TH_Eins_Break;
         procedure TH_Eins_Resume;
         procedure TH_Eins_Stop;
         procedure TH_Zwei_Start (breaked: boolean);
         procedure TH_Zwei_Break;
         procedure TH_Zwei_Resume;
         procedure TH_Zwei_Stop;
         procedure TH_Uhr_Start (breaked: boolean);
         procedure TH_Uhr_Stop;
   end;

var  MyThreads: TMyThreads;

implementation

{ TMyThreads }

constructor TMyThreads.Create;
begin

end;

//TH_Eins
procedure TMyThreads.TH_Eins_Start(breaked: boolean);
begin
   fTH_Eins := TMyThread_Eins.Create(breaked);
end;

procedure TMyThreads.TH_Eins_Break;
begin
   if not fTH_Eins.Terminated then begin
      fTH_Eins.Suspend;
   end;
end;

procedure TMyThreads.TH_Eins_Resume;
begin
   if not fTH_Eins.Terminated then begin
      fTH_Eins.Resume;
   end;
end;

procedure TMyThreads.TH_Eins_Stop;
begin
   if assigned(fTH_Eins) then begin
      if not fTH_Eins.Terminated then begin
         fTH_Eins.Terminate;
      end;
   end;
end;

//TH_Zwei
procedure TMyThreads.TH_Zwei_Start(breaked: boolean);
begin
   fTH_Zwei := TMyThread_Zwei.Create(breaked);
end;

procedure TMyThreads.TH_Zwei_Break;
begin
   if not fTH_Zwei.Terminated then begin
      fTH_Zwei.Suspend;
   end;
end;

procedure TMyThreads.TH_Zwei_Resume;
begin
   if not fTH_Zwei.Terminated then begin
      fTH_Zwei.Resume;
   end;
end;

procedure TMyThreads.TH_Zwei_Stop;
begin
   if assigned(fTH_Zwei) then begin
      if not fTH_Zwei.Terminated then begin
         fTH_Zwei.Terminate;
      end;
   end;
end;

//Uhr
procedure TMyThreads.TH_Uhr_Start(breaked: boolean);
begin
   fTH_Uhr := TMyThread_Uhr.Create(breaked);
end;

procedure TMyThreads.TH_Uhr_Stop;
begin
   if assigned(fTH_Uhr) then begin
      if not fTH_Uhr.Terminated then begin
         fTH_Uhr.Terminate;
      end;
   end;
end;

{ TMyTread_Eins }

procedure TMyThread_Eins.Execute;
var  I: integer;
      sEins: integer;
      c_Eins: integer;
begin
   sEins := 1000;
   c_Eins := 0;
   Synchronize(procedure
               begin
                  if Assigned(fWriteTHEinsValue) then
                     fWriteTHEinsValue(c_Eins);
               end);
   for I := 0 to 19 do begin
      if Terminated then begin
         Self.Free;
         Break;
      end;
      sleep(sEins);
      INC(c_Eins);
      if Terminated then begin
         Self.Free;
         Break;
      end;
      Synchronize(procedure
                  begin
                     if Assigned(fWriteTHEinsValue) then
                        fWriteTHEinsValue(c_Eins);
                  end);
   end;
end;

{ TMyThread_Zwei }

procedure TMyThread_Zwei.Execute;
var  I: integer;
      sZwei: integer;
      c_Zwei: integer;
begin
   sZwei := 1000;
   c_Zwei := 0;
   Synchronize(procedure
               begin
                  if Assigned(fWriteTHZweiValue) then
                     fWriteTHZweiValue(c_Zwei);
               end);
   for I := 0 to 19 do begin
      if Terminated then begin
         Self.Free;
         Break;
      end;
      sleep(sZwei);
      INC(c_Zwei);
      if Terminated then begin
         Self.Free;
         Break;
      end;
      Synchronize(procedure
               begin
                  if Assigned(fWriteTHZweiValue) then
                     fWriteTHZweiValue(c_Zwei);
               end);
   end;
end;

{ TMyThread_Uhr }

procedure TMyThread_Uhr.Execute;
var  I: integer;
      sUhr: integer;
      O: TObject;
      T: TTime;
begin
   sUhr := 1000;
   while not Terminated do begin
      sleep(sUhr);
      Synchronize(procedure
                  begin
                     if Assigned(fWriteClockValue) then
                        fWriteClockValue(Now());
                  end);
   end;
end;

end.
Meine aufrufendes Formular
Delphi-Quellcode:
unit Frm_Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, TMyThreadUnit;

type
   TfrmMain = class(TForm)
      lbl_eins: TLabel;
      lbl_zwei: TLabel;
      btn_eins: TButton;
      btn_zwei: TButton;
      lbl_Zeit: TLabel;
      Timer1: TTimer;
      Timer_Eins_Start: TButton;
      Timer_Zwei_Start: TButton;
      Timer_Eins_Pause: TButton;
      Timer_Zwei_Pause: TButton;
      Timer_Eins_Resume: TButton;
      Timer_Zwei_Resume: TButton;
      Timer_Eins_Stop: TButton;
      Timer_Zwei_Stop: TButton;
      btn_EinsZwei: TButton;
      pnl_MultiThreads: TPanel;
      pnl_MainThread: TPanel;
      pnl_Results: TPanel;
      procedure btn_einsClick(Sender: TObject);
      procedure btn_zweiClick(Sender: TObject);
      procedure Timer1Timer(Sender: TObject);
      procedure Timer_Eins_StartClick(Sender: TObject);
      procedure Timer_Eins_PauseClick(Sender: TObject);
      procedure Timer_Eins_ResumeClick(Sender: TObject);
      procedure Timer_Eins_StopClick(Sender: TObject);
      procedure Timer_Zwei_StartClick(Sender: TObject);
      procedure Timer_Zwei_PauseClick(Sender: TObject);
      procedure Timer_Zwei_ResumeClick(Sender: TObject);
      procedure Timer_Zwei_StopClick(Sender: TObject);
      procedure btn_EinsZweiClick(Sender: TObject);
      procedure FormShow(Sender: TObject);
      procedure FormCreate(Sender: TObject);
      procedure FormDestroy(Sender: TObject);
   private
      { Private-Deklarationen }
      MyThread: TMyThreads;
   public
      { Public-Deklarationen }
      //Alles im Main-Thread
      procedure Timer_Eins;
      procedure Timer_Zwei;
      //Wird vom MyThread aufgerufen
      procedure Write_Counter_Eins(Counter_Eins: integer);
      procedure Write_Counter_Zwei (Counter_Zwei: integer);
      procedure Write_Uhr (Zeit: TTime);
   end;

var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

//Alles im MainThread
procedure TfrmMain.Timer_Eins;
var  I: integer;
      sEins: integer;
      c_Eins: integer;
begin
   sEins := 1000;
   c_Eins := 0;
   lbl_eins.Caption := IntToStr(c_Eins);
   lbl_eins.Refresh;
   for I := 0 to 9 do begin
      sleep(sEins);
      INC(c_Eins);
      lbl_eins.Caption := IntToStr(c_Eins);
      lbl_eins.Refresh;
   end;
end;

procedure TfrmMain.Timer_Zwei;
var  I: integer;
      sZwei: integer;
      c_Zwei: integer;
begin
   sZwei := 1000;
   c_Zwei := 0;
   lbl_zwei.Caption := IntToStr(c_Zwei);
   lbl_zwei.Refresh;
   for I := 0 to 9 do begin
      sleep(sZwei);
      INC(c_Zwei);
      lbl_zwei.Caption := IntToStr(c_Zwei);
      lbl_zwei.Refresh;
   end;
end;

procedure TfrmMain.Timer1Timer(Sender: TObject);
begin
   //lbl_Zeit.Caption := TimeToStr(now());
end;

//OnCreate, OnShow, OnDestroy Proceduren
procedure TfrmMain.FormCreate(Sender: TObject);
begin
   MyThread := TMyThreads.Create;
end;

procedure TfrmMain.FormShow(Sender: TObject);
begin
   MyThread.TH_Uhr_Start(True);
   MyThread.TH_Uhr.WriteClockValue := Write_Uhr;
   MyThread.TH_Uhr.Resume;
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
   MyThread.TH_Uhr_Stop;
   MyThread.TH_Eins_Stop;
   MyThread.TH_Zwei_Stop;
   MyThread.Free;
end;

//ClickProceduren
//Im MainThread
procedure TfrmMain.btn_einsClick(Sender: TObject);
begin
   Timer_Eins;
end;

procedure TfrmMain.btn_zweiClick(Sender: TObject);
begin
   Timer_Zwei;
end;

procedure TfrmMain.btn_EinsZweiClick(Sender: TObject);
begin
   MyThread.TH_Eins_Start(true);
   MyThread.TH_Eins.WriteTHEinsValue := Write_Counter_Eins;
   MyThread.TH_Eins.Resume;

   MyThread.TH_Zwei_Start(true);
   MyThread.TH_Zwei.WriteTHZweiValue := Write_Counter_Zwei;
   MyThread.TH_Zwei.Resume;
end;

//MultiThreads
//TH_Eins Ckick
procedure TfrmMain.Timer_Eins_StartClick(Sender: TObject);
begin
   MyThread.TH_Eins_Start(true);
   MyThread.TH_Eins.WriteTHEinsValue := Write_Counter_Eins;
   MyThread.TH_Eins.Resume;
end;

procedure TfrmMain.Timer_Eins_PauseClick(Sender: TObject);
begin
   MyThread.TH_Eins_Break;
end;

procedure TfrmMain.Timer_Eins_ResumeClick(Sender: TObject);
begin
   MyThread.TH_Eins_Resume;
end;

procedure TfrmMain.Timer_Eins_StopClick(Sender: TObject);
begin
   MyThread.TH_Eins_Stop;
end;
//TH_Zwei Click
procedure TfrmMain.Timer_Zwei_StartClick(Sender: TObject);
begin
   MyThread.TH_Zwei_Start(true);
   MyThread.TH_Zwei.WriteTHZweiValue := Write_Counter_Zwei;
   MyThread.TH_Zwei.Resume;
end;

procedure TfrmMain.Timer_Zwei_PauseClick(Sender: TObject);
begin
   MyThread.TH_Zwei_Break;
end;

procedure TfrmMain.Timer_Zwei_ResumeClick(Sender: TObject);
begin
   MyThread.TH_Zwei_Resume;
end;

procedure TfrmMain.Timer_Zwei_StopClick(Sender: TObject);
begin
   MyThread.TH_Zwei_Stop;
end;

//Schreibproceduren
procedure TfrmMain.Write_Counter_Eins(Counter_Eins: integer);
begin
   lbl_eins.Caption := IntToStr(Counter_Eins);
   lbl_eins.Refresh;
end;

procedure TfrmMain.Write_Counter_Zwei(Counter_Zwei: integer);
begin
   lbl_zwei.Caption := IntToStr(Counter_Zwei);
   lbl_zwei.Refresh;
end;

procedure TfrmMain.Write_Uhr(Zeit: TTime);
begin
   lbl_Zeit.Caption := TimeToStr(Zeit);
   lbl_Zeit.Refresh;
end;

end.

BerndS 28. Nov 2023 15:02

AW: Erste Schritte Multi-Threading
 
Ohne mir das alles genauer angeschaut zu haben, würde ich im
Delphi-Quellcode:
   for I := 0 to 19 do begin
      if Terminated then begin
         Self.Free;
         Break;
      end;
das Self.Free entfernen.
Wenn du erreichen willst, das sich der Thread selbst frei gibt, dann verwende
Delphi-Quellcode:
FreeOnTerminate := True;


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