Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Allgemeine Fragen zu Threads (https://www.delphipraxis.net/191535-allgemeine-fragen-zu-threads.html)

norwegen60 25. Jan 2017 13:17

Delphi-Version: 10 Seattle

Allgemeine Fragen zu Threads
 
Hallo

ich habe mal ausgehend von dem Beispiel in http://docwiki.embarcadero.com/CodeE...dList_(Delphi) folgenden (sinnfreien) Code geschrieben. Ich weiß z.B. dass es besser wäre, in der Memo nur die fehlenden Werte zu ergänzen. Ich wollte den Code aber flach halten:
Delphi-Quellcode:
unit fo_ThreadListDePraxis;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Generics.Collections,
  Vcl.Samples.Spin;

type
  TForm1 = class(TForm)
    laSleeptime: TLabel;
    Memo1: TMemo;
    Button1: TButton;
    Button2: TButton;
    SpinEdit1: TSpinEdit;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure Button2Click(Sender: TObject);
    procedure SpinEdit1Change(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

  TMyList = class(TList<Integer>)
  end;

  TMyThread = class(TThread)
  private
    FCounter, FSleepTime: Integer;
  protected
    procedure Execute; override;
  public
    property SleepTime: Integer read FSleepTime write FSleepTime;
  end;

var
  Form1:   TForm1;
  MyList:  TMyList;
  MyThread: TMyThread;

implementation

{$R *.dfm}

procedure TForm1.Button2Click(Sender: TObject);
begin
  if assigned(MyThread) then
  begin
    MyThread.Terminate;                // Setzt MyThread nicht auf NIL, d.h. nächster Button2Click führt wieder in diesn Block
    Button2.Caption := 'Start Thread';
  end
  else
  begin
    Button2.Caption := 'Terminate Thread';
    MyThread := TMyThread.Create(true); // Create suspended--secondProcess does not run yet.
    MyThread.FreeOnTerminate := true;  // You do not need to clean up after termination.
    MyThread.SleepTime := SpinEdit1.Value;
    MyThread.Resume; // Now run the thread.
  end;
end;

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

procedure TForm1.FormCreate(Sender: TObject);
begin
  MyList := TMyList.Create;
end;

procedure TForm1.SpinEdit1Change(Sender: TObject);
begin
  if assigned(MyThread) then
    MyThread.SleepTime := SpinEdit1.Value;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
begin
  for i := 0 to 20 do
    MyList.Add(random(200));
end;

{ TMyThread }

procedure TMyThread.Execute;
var
  i: Integer;
begin
  inherited;

  while (true) do
  begin
    if Terminated then
      exit;

    inc(FCounter);
    Form1.Label1.Caption := IntToStr(FCounter);
    Form1.Memo1.Clear;

    System.TMonitor.Enter(MyList); // Liste gegen Zweitzugriff sperren. Muss das sein?
    try
      for i := 0 to MyList.Count - 1 do
      begin
        if Terminated then
          exit;
        Form1.Memo1.Lines.Add(format('%d: %d', [i, MyList.Items[i]])); // Ist das zulässig?
      end;
    finally
      System.TMonitor.exit(MyList); // Sperrung der Liste wieder aufheben
    end;
    sleep(FSleepTime); // FSleepTime oder besser SleepTime?
  end;
end;

end.
Meine Fragen:
  1. Überall heißt es man soll aus Threads nicht direkt auf Form-Elemente zugreifen. Hier wird es doch gemacht. Also doch kein Problem wenn sicher ist dass kein anderer auf Form1.Memo1 zugreift? Und abgesehen davon dass es sauberer ist die Trennung per Synchronize zu machen?
  2. Im Execute greife ich auf die globale Liste MyList zu. Muss ich die zwingend mit
    Delphi-Quellcode:
    System.TMonitor.Enter
    sperren wenn ich nur lesen will?
    • Was kann passieren wenn ich nur Werte hinzufüge?
    • Was wenn sich auch Werte ändern könnten?
  3. Obwohl ich
    Delphi-Quellcode:
    MyThread.FreeOnTerminate := true;
    gesetzt habe funktioniert die Assign-Abfrage in
    Delphi-Quellcode:
    procedure TForm1.Button2Click(Sender: TObject)
    . Setze ich einen Haltepunkt nach dem Terminate ist MyThread nicht NIL.
  4. Die Anwendung wirft in der IDE eine Exception wenn in FormClose
    Delphi-Quellcode:
    MyThread.Terminate;
    nicht aufgerufen wird. Muss ich beim Beenden sonst noch was beachten?
  5. Greift man innerhalb einer Klasse besser auf die Variable oder das Property zu (FSleepTime oder SleeptTime)?

Danke für euer Feedback
Gerd

a.def 25. Jan 2017 13:32

AW: Allgemeine Fragen zu Theads
 
Soweit ich (nun) weiß müssen lesende als auch schreibende Zugriffe geschützt werden.
Statt TMonitor würde ich aber CriticalSections benutzen. Die Ausführung ist weitaus schneller.

Hier wird ja nur die Liste geschützt. Das Memo aber nicht. Das Emba-Beispiel ist keines, welches man sich "näher" ansehen sollte finde ich.

Uwe Raabe 25. Jan 2017 13:35

AW: Allgemeine Fragen zu Theads
 
Zitat:

Zitat von norwegen60 (Beitrag 1359916)
Überall heißt es man soll aus Threads nicht direkt auf Form-Elemente zugreifen. Hier wird es doch gemacht. Also doch kein Problem wenn sicher ist dass kein anderer auf Form1.Memo1 zugreift?

Das ist so nicht richtig. Korrekt müsste es heißen: "wenn sicher ist, dass kein anderer auf die VCL zugreift", aber das kannst du einfach nicht sicherstellen, wenn das nicht im Hauptthread abläuft. Hintergrund: die VCL kapselt die Winapi-Zugriffe und legt dafür dynamisch die benötigten Handles an, sobald sie gebraucht werden. Da es sich hierbei auch schon mal um globale Variablen oder Objekte handelt, ist eine Verwendung in getrennten Threads potentiell tödlich für die Anwendung. Deswegen die Maßgabe: alle Zugriffe auf die VCL ausschließlich aus dem Hauptthread heraus!

Zitat:

Zitat von norwegen60 (Beitrag 1359916)
Im Execute greife ich auf die globale Liste MyList zu. Muss ich die zwingend mit
Delphi-Quellcode:
System.TMonitor.Enter
sperren wenn ich nur lesen will?

Solange die Liste überhaupt nicht verändert wird, sollte das Lesen funktionieren. Sobald aber irgendwer eventuell eine Änderung machen könnte, ist das nicht mehr der Fall.

Zitat:

Zitat von norwegen60 (Beitrag 1359916)
Was kann passieren wenn ich nur Werte hinzufüge?

Sobald dabei mehrere Threads beteiligt sind (Lesen und/oder Schreiben) muss synchronisiert werden. Beim Schreiben kann es vorkommen, daß der der Speicher neu alloziert wird. Davon wäre auch ein Lesen betroffen.

Zitat:

Zitat von norwegen60 (Beitrag 1359916)
Was wenn sich auch Werte ändern könnten?

Da auch hier keine atomare Operation angenommen werden kann, ist ein Synchronisieren notwendig.

Zitat:

Zitat von norwegen60 (Beitrag 1359916)
Obwohl ich
Delphi-Quellcode:
MyThread.FreeOnTerminate := true;
gesetzt habe funktioniert die Assign-Abfrage in
Delphi-Quellcode:
procedure TForm1.Button2Click(Sender: TObject)
. Setze ich einen Haltepunkt nach dem Terminate ist MyThread nicht NIL.

Das Free gibt ja auch nur die Instanz frei, setzt aber deine Variable nicht auf nil.

Zitat:

Zitat von norwegen60 (Beitrag 1359916)
Die Anwendung wirft in der IDE eine Exception wenn in FormClose
Delphi-Quellcode:
MyThread.Terminate;
nicht aufgerufen wird. Muss ich beim Beenden sonst noch was beachten?

Siehe oben.

Zitat:

Zitat von norwegen60 (Beitrag 1359916)
Greift man innerhalb einer Klasse besser auf die Variable oder das Property zu (FSleepTime oder SleeptTime)?

Das ist in diesem Fall Geschmackssache.

sakura 25. Jan 2017 13:40

AW: Allgemeine Fragen zu Theads
 
Zitat:

Zitat von norwegen60 (Beitrag 1359916)
[*]Überall heißt es man soll aus Threads nicht direkt auf Form-Elemente zugreifen. Hier wird es doch gemacht. Also doch kein Problem wenn sicher ist dass kein anderer auf Form1.Memo1 zugreift? Und abgesehen davon dass es sauberer ist die Trennung per Synchronize zu machen?

Nur weil es gemacht wird, ist es nicht korrekt. Die Beispiele sind oft von Testern aus der Beta-Phase, nicht vom Embarcadero selbst.
Zitat:

Zitat von norwegen60 (Beitrag 1359916)
[*]Im Execute greife ich auf die globale Liste MyList zu. Muss ich die zwingend mit
Delphi-Quellcode:
System.TMonitor.Enter
sperren wenn ich nur lesen will?

Es gibt, wie oben schon beschrieben auch andere Möglichkeiten, aber eine dieser solltest tue nutzen.
Zitat:

Zitat von norwegen60 (Beitrag 1359916)
[*]Was kann passieren wenn ich nur Werte hinzufüge?

Zugriffsverletzungen, invalide Daten, Nichts... je nach dem, was andere Threads gerade machen
Zitat:

Zitat von norwegen60 (Beitrag 1359916)
[*]Was wenn sich auch Werte ändern könnten?

^^
Zitat:

Zitat von norwegen60 (Beitrag 1359916)
[*]Obwohl ich
Delphi-Quellcode:
MyThread.FreeOnTerminate := true;
gesetzt habe funktioniert die Assign-Abfrage in
Delphi-Quellcode:
procedure TForm1.Button2Click(Sender: TObject)
. Setze ich einen Haltepunkt nach dem Terminate ist MyThread nicht NIL.

Free gibt das Objekt frei, die Variabeln werden aber nicht beeinflusst.
Zitat:

Zitat von norwegen60 (Beitrag 1359916)
[*]Greift man innerhalb einer Klasse besser auf die Variable oder das Property zu (FSleepTime oder SleeptTime)?

SleepTime - dann lässt sich die Implementierung ändern, ohne dass Du überall im Quellcode nachschauen musst.

...:cat:...

a.def 25. Jan 2017 13:41

AW: Allgemeine Fragen zu Theads
 
Wenn die Profis schon sprechen (ihr):
was haltet ihr denn von TMonitor und CriticalSections? Welches bevorzugt ihr? Soweit ich weiß hat CriticalSections weniger Overhead und ist deutlich schneller als TMonitor.

Wenn ich darf habe ich auch eine grundlegende Frage zur Synchronisation:
wenn man zwei Threads hat, beide greifen auf die VLC zu (Memo.Lines.Add) und man das synchronisiert, verliert man dann nicht den Vorteil den mehrere Threads eigentlich bieten sollten?

sakura 25. Jan 2017 13:46

AW: Allgemeine Fragen zu Theads
 
Zitat:

Zitat von a.def (Beitrag 1359922)
TMonitor und CriticalSections? Welches bevorzugst du?

Alle haben ihre Vor- und Nachteile und es gibt noch mehr. Das hängt wirklich davon ab, was Du erreichen willst. Hier hilft nur studieren und die entsprechende Situation analysieren. Zum Über ist TCriticalSection aber ein guter Anfang. Dann gibt es auch noch TMultiReadExclusiveWriteSynchronizer, TSpinLock, TSpinWait, TSemaphore, TMutex, und, und, und...

...:cat:...

sakura 25. Jan 2017 13:48

AW: Allgemeine Fragen zu Theads
 
Zitat:

Zitat von a.def (Beitrag 1359922)
wenn man zwei Threads hat, beide greifen auf die VLC zu (Memo.Lines.Add) und man das synchronisiert, verliert man dann nicht den Vorteil den mehrere Threads eigentlich bieten sollten?

Gut möglich, es kommt auch darauf an, wie lange die eigentliche Arbeit im Vergleich zur Synchronisation braucht. Grundsätzlich solltest Du versuchen auf Synchronisation mit der VCL zu verzichten und die Daten anderweitig zur Verfügung zu stellen und diese dann in der VCL-Anwendung abfragen.

...:cat:...

HolgerX 25. Jan 2017 13:54

AW: Allgemeine Fragen zu Theads
 
Hmmm..

Damit deine Thread-Variable auf nil gesetzt wird, brauchst Du so etwas:

( Und wieso die Globale Variable 'MyThread', obwohl nur innerhalb der Form darauf zugegriffen wird !!) ;)
Delphi-Quellcode:
  TForm1 = class(TForm)
    ...
    procedure Button2Click(Sender: TObject);
  private
    procedure OnMyThreadTerminate(Sender: TObject);
  protected
    MyThread : TMyThread;
  public
    { Public-Deklarationen }
  end;


procedure TForm1.Button2Click(Sender: TObject);
begin
  if assigned(MyThread) then
  begin
    MyThread.Terminate; // Setzt MyThread nicht auf NIL, d.h. nächster Button2Click führt wieder in diesn Block
    Button2.Enabled := false; // Erst benutzbar machen, wenn Thread wirklich weg!
  end
  else
  begin
    Button2.Caption := 'Terminate Thread';
    MyThread := TMyThread.Create(true); // Create suspended--secondProcess does not run yet.
    MyThread.FreeOnTerminate := true; // You do not need to clean up after termination.

    MyThread.OnTerminate := OnMyThreadTerminate; // Damit die Form benachrichtigt wird !!!!

    MyThread.SleepTime := SpinEdit1.Value;
    MyThread.Resume; // Now run the thread.
  end;
end;

procedure TForm1.OnMyThreadTerminate(Sender: TObject);
begin
  if Sender = MyThread then begin
    MyThread := nil; // Nur nil, Free macht er selber!
    Button2.Caption := 'Start Thread';
    Button2.Enabled := true; // Erst benutzbar machen, wenn Thread wirklich weg!
  end;
end;

Uwe Raabe 25. Jan 2017 13:57

AW: Allgemeine Fragen zu Theads
 
Zitat:

Zitat von a.def (Beitrag 1359922)
was haltet ihr denn von TMonitor und CriticalSections? Welches bevorzugt ihr? Soweit ich weiß hat CriticalSections weniger Overhead und ist deutlich schneller als TMonitor.

Das sollte seit XE5 kein Problem mehr sein.

Zitat:

Zitat von a.def (Beitrag 1359922)
Wenn ich darf habe ich auch eine grundlegende Frage zur Synchronisation:
wenn man zwei Threads hat, beide greifen auf die VLC zu (Memo.Lines.Add) und man das synchronisiert, verliert man dann nicht den Vorteil den mehrere Threads eigentlich bieten sollten?

Wenn die Hauptaufgabe in dem synchronisierten Part steckt, ja. Wenn sich das auch nicht ändern lässt, dann hat man wenig Chancen und kann vermutlich ganz auf die Threads verzichten. Geht es darum, eine längere Aktion nur im VCL-Thread auszuführen, ohne die UI zu blocken, lässt sich das auch durch einen StateMachine-Ansatz lösen.

norwegen60 25. Jan 2017 14:39

AW: Allgemeine Fragen zu Theads
 
Ihr seit einfach super.

Zitat:

Zitat von Uwe Raabe (Beitrag 1359920)
Korrekt müsste es heißen: "wenn sicher ist, dass kein anderer auf die VCL zugreift", ...

Ja, so dachte ich mir das.

Zitat:

Zitat von Uwe Raabe (Beitrag 1359920)
Das Free gibt ja auch nur die Instanz frei, setzt aber deine Variable nicht auf nil.

Ja, klar, heißt ja nicht FreeAndNil

Zitat:

Zitat von a.def (Beitrag 1359919)
Soweit ich (nun) weiß müssen lesende als auch schreibende Zugriffe geschützt werden.
Statt TMonitor würde ich aber CriticalSections benutzen. Die Ausführung ist weitaus schneller.

Das widerspricht sich jetzt mit
Zitat:

Zitat von Uwe Raabe (Beitrag 1359930)
Zitat:

Zitat von a.def (Beitrag 1359922)
was haltet ihr denn von TMonitor und CriticalSections? Welches bevorzugt ihr? Soweit ich weiß hat CriticalSections weniger Overhead und ist deutlich schneller als TMonitor.

Das sollte seit XE5 kein Problem mehr sein.

Irgendwann schau ich mr auch mal die Themen CriticalSection, TMultiReadExclusiveWriteSynchronizer, TSpinLock, TSpinWait, TSemaphore, TMutex, und, und, und... an, aber im Moment muss ich es erst mal setzen lassen und etwas rum probieren.

Danke


Alle Zeitangaben in WEZ +1. Es ist jetzt 12:33 Uhr.
Seite 1 von 2  1 2      

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