Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   TThread: Synchronize() priorisieren? (https://www.delphipraxis.net/188696-tthread-synchronize-priorisieren.html)

romber 29. Mär 2016 11:42

TThread: Synchronize() priorisieren?
 
Hallo!

Ich habe mehrere Threads, die zeitkritische Daten aus unterschiedlichen Quellen geliefert bekommen. Die Threads haben alle eins gemeinsam: irgendwan greifen die alle an ein bestimmtes GUI-Objekt zu, synchroniziert natürlich. Besteht die Möglichkeit, einen Thread so zu priorisieren, dass er immer Vorrang bei synchronisiertem Zugriff auf die GUI hat? Dabei möchten ich keinesfalls die gesammte Thread-Prioritöt verändern, sondern nur das es bei Synchronize höhere Priorität hat.
Geht das irgendwie?

Danke!

himitsu 29. Mär 2016 11:46

AW: TThread: Synchronize() priorisieren?
 
Nein, wer zuerst kommt, malt zuerst.
Die Liste der Synchronisationen wird der Reihe nach abgearbeitet.

Wenn nicht gewünscht, dann wirst du die Synchronisierung wohl selber implementieren müssen.
z.B. die "Callbacks" in eine TThreadList eintragen, dann eine Message (Send oder Post) an den Hauptthread schicken und da diese Liste priorisiert abarbeiten.

Der schöne Günther 29. Mär 2016 11:53

AW: TThread: Synchronize() priorisieren?
 
Was er sagt: Synchronize ist ein blockierender Aufruf. Er stellt dem Hauptthread einen Methodenzeiger in einer "Hier, arbeite das ab"-Warteschlange hinten an. Die Reihenfolge könnte man vielleicht mit viel Gefummel verändern, aber ich glaube was du vorhast ist nicht der beste Ansatz.

Der Thread sollte gar nichts mit der Oberfläche zu tun haben sondern nur die jeweils aktuellen Daten bereitstellen. Wenn es neue Daten gibt, kann er ja ein Event auslösen und die Oberfläche zeigt die jeweils aktuellen Daten an wie sie grade Lust hat.

Uwe Raabe 29. Mär 2016 12:35

AW: TThread: Synchronize() priorisieren?
 
Man sollte auch prüfen, ob das
Delphi-Quellcode:
Synchronize
hier nicht durch ein
Delphi-Quellcode:
Queue
ersetzt werden kann. Solange der Thread nicht auf das Ergebnis bzw. den Abschluss der Synchronize-Operation angewiesen ist, wäre Queue (non-blocking) hier vielleicht die bessere Wahl.

romber 29. Mär 2016 15:37

AW: TThread: Synchronize() priorisieren?
 
Danke für die schnelle Reaktionen!

Zitat:

Zitat von Der schöne Günther (Beitrag 1334051)
Der Thread sollte gar nichts mit der Oberfläche zu tun haben sondern nur die jeweils aktuellen Daten bereitstellen. Wenn es neue Daten gibt, kann er ja ein Event auslösen und die Oberfläche zeigt die jeweils aktuellen Daten an wie sie grade Lust hat.

Das habe ich jetzt nicht ganz verstanden :oops: In meinem Fall mache ich doch nichts anderes mit Synchronize(). Ich rufe damit eine Metode des Grids (GUI), die für die Visualisierung der Daten zuständig ist. Oder was ist mit einem Event gemeint?

Zitat:

Zitat von Uwe Raabe (Beitrag 1334054)
Man sollte auch prüfen, ob das
Delphi-Quellcode:
Synchronize
hier nicht durch ein
Delphi-Quellcode:
Queue
ersetzt werden kann. Solange der Thread nicht auf das Ergebnis bzw. den Abschluss der Synchronize-Operation angewiesen ist, wäre Queue (non-blocking) hier vielleicht die bessere Wahl.

Danfür für diesen Hinweis. Die Threads sind tatsächlich nicht auf den Ergebnis der Synchronize angewiesen. Bringt Queue() neben dem Vorteil, dass der Threadablauf nicht blockiert wird, irgendwelche Nachteile mit sich?

Uwe Raabe 29. Mär 2016 16:02

AW: TThread: Synchronize() priorisieren?
 
Zitat:

Zitat von romber (Beitrag 1334062)
Bringt Queue() neben dem Vorteil, dass der Threadablauf nicht blockiert wird, irgendwelche Nachteile mit sich?

Nicht, daß ich wüsste.
Synchronize = Arbeite diese Methode bei nächter Gelegenheit im Hauptthread ab und warte solange bis sie fertig ist.
Queue = Arbeite diese Methode bei nächter Gelegenheit im Hauptthread ab.
Es kann allerdings passieren, daß bei Queue die Methode gar nicht abgearbeitet wird, da bei Freigabe der TThread-Instanz alle noch vorhandenen Queue-Events gelöscht werden. Dem kann man aber vorbeugen, in dem man anstatt der einfachen Queue-Methode die gleichnamige Klassenmethode mit dem zusätzlichen TThread-Parameter an erster Stelle verwendet und diesen als nil übergibt. Dann kann das zwar theoretisch immer noch passieren, aber nur wenn das ganze Programm vor Abarbeiten des Queue-Events beendet wird. Das wäre übrigens bei Synchronize auch der Fall.

Der schöne Günther 29. Mär 2016 16:06

AW: TThread: Synchronize() priorisieren?
 
Zitat:

Zitat von romber (Beitrag 1334062)
Ich rufe damit eine Metode des Grids (GUI), die für die Visualisierung der Daten zuständig ist. Oder was ist mit einem Event gemeint?

Mit dem Event war gemeint, den Thread nicht von deiner Oberfläche abhängig zu machen. Den Thread sollte es nichts angehen, was wie auf welche Labels kommt, sondern man sollte ihm von außen übergeben, was getan werden soll wenn neue Daten gesammelt wurden. Dann kannst man auch die Oberfläche komplett überarbeiten ohne die Logik im Hintergrund wieder anfassen zu müssen. Oder die Threads in einer anderen Anwendung wiederverwenden. Oder man kommt nicht aus dem Tritt wenn die Temperatur bei manchen Kunden nun bitte in Fahrenheit und nicht Celsius angezeigt werden soll.


Als Beispiel mit
Delphi-Quellcode:
TNotifyEvent
, im Endeffekt das gleiche wie das
Delphi-Quellcode:
OnClick
bei einem
Delphi-Quellcode:
TButton
:

Formular:
(Braucht ein Label "airPressureLabel" und ein "temperatureLabel")
Delphi-Quellcode:
unit Unit3;

interface

uses
   System.SysUtils, System.SyncObjs, System.Classes,
   Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
   Threads
;

type
   TForm3 = class(TForm)
      temperatureLabel: TLabel;
      airPressureLabel: TLabel;
      procedure FormCreate(Sender: TObject);
      private var
         airPressureThread: TPressureThread;
         temperatureThread: TTemperatureThread;
      private
         procedure handleNewData(Sender: TObject);
   end;

var
   Form3: TForm3;

implementation

{$R *.dfm}

procedure TForm3.FormCreate(Sender: TObject);
begin
   airPressureThread := TPressureThread.Create(True);
   temperatureThread := TTemperatureThread.Create(True);

   airPressureThread.OnNewData := handleNewData;
   temperatureThread.OnNewData := handleNewData;

   airPressureThread.Start();
   temperatureThread.Start();
end;

procedure TForm3.handleNewData(Sender: TObject);
var
   value: Single;
begin
   if (Sender = airPressureThread) then begin
      value := airPressureThread.getAirPressure();
      airPressureLabel.Caption := String.Format('Pressure: %f Pa', [value]);
   end;

   if (Sender = temperatureThread) then begin
      value := temperatureThread.getTemperature();
      temperatureLabel.Caption := String.Format('Temperature: %f °C', [value]);
   end;
end;

end.
Zweite Unit "Threads":

Delphi-Quellcode:
unit Threads;

interface uses System.Classes, System.SyncObjs;

type
   /// <summary>
   ///     Abstrakte Oberklasse für Threads die Daten
   ///    bereitstellen und mit <c>OnNewData</c> signalisieren
   ///    dass neue Daten bereitliegen
   /// </summary>
   TDataThread = class(TThread)
      protected var
         mutex: TSynchroObject;
      protected
         procedure triggerNewDataEvent();
      public var
         /// <remarks>
         ///     Wird immer <b>im Hauptthread</b> ausgeführt
         /// </remarks>
         OnNewData: TNotifyEvent;
      public
         constructor Create(const CreateSuspended: Boolean);
         destructor Destroy(); override;
   end;

   TTemperatureThread = class(TDataThread)
      protected const
         noTemperature = -273.15;
      protected var
         FTemperature: Single;
      protected
         function retrieveTemperature(): Single;
         procedure Execute(); override;
      public
         constructor Create(const CreateSuspended: Boolean);
         /// <returns>
         ///     Temperatur in °C
         /// </returns>
         /// <remarks>
         ///     Wenn noch keine Temperatur aufgezeichnet wurde wird
         ///    <c>-273.15</c> zurückgegeben
         /// </remarks>
         function getTemperature(): Single;
   end;

   TPressureThread = class(TDataThread)
      protected procedure Execute(); override;
      /// <returns>
      ///     Druck in Pa
      /// </returns>
      public function getAirPressure(): Single;
   end;

implementation

{ TDataThread }

constructor TDataThread.Create(const CreateSuspended: Boolean);
begin
   inherited Create(CreateSuspended);
   mutex := TCriticalSection.Create();
end;

destructor TDataThread.Destroy();
begin
   mutex.Free();
   inherited;
end;

procedure TDataThread.triggerNewDataEvent();
begin
   if not Assigned(OnNewData) then Exit;
   TThread.Queue(
      nil,
      procedure()
      begin
         OnNewData(self);
      end
   );
end;

{ TTemperatureThread }

constructor TTemperatureThread.Create(const CreateSuspended: Boolean);
begin
   inherited Create(CreateSuspended);
   FTemperature := noTemperature;
end;

procedure TTemperatureThread.Execute();
var
   currentTemperature: Single;
begin
   while (not Terminated) do begin
      currentTemperature := retrieveTemperature();
      if (currentTemperature = noTemperature) then Continue;

      mutex.Acquire();
      try
         FTemperature := currentTemperature;
      finally
         mutex.Release();
      end;

      triggerNewDataEvent();
   end;
end;

function TTemperatureThread.getTemperature(): Single;
begin
   mutex.Acquire();
   try
      Result := FTemperature;
   finally
      mutex.Release();
    end;
end;

function TTemperatureThread.retrieveTemperature(): Single;
begin
   Sleep( 1000 + Random(1000) );
   Result := 42.0 + Random() * 10.0;
end;

{ TPressureThread }

procedure TPressureThread.Execute();
begin
   while (not Terminated) do begin
      // Platzhalter
    end;

end;

function TPressureThread.getAirPressure(): Single;
begin
   // Platzhalter
end;


end.

Mavarik 29. Mär 2016 16:32

AW: TThread: Synchronize() priorisieren?
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1334051)
Der Thread sollte gar nichts mit der Oberfläche zu tun haben sondern nur die jeweils aktuellen Daten bereitstellen. Wenn es neue Daten gibt, kann er ja ein Event auslösen und die Oberfläche zeigt die jeweils aktuellen Daten an wie sie grade Lust hat.

emm... nix anderes ist ein Syncronize... Ob Du nun wie Du im Beispiel den Event im Queue auslößt oder mit Sync es aus dem Thread selber machst... Ich erstmal "fast" kein Unterschied...

Zitat:

Zitat von romber (Beitrag 1334062)
Bringt Queue() neben dem Vorteil, dass der Threadablauf nicht blockiert wird, irgendwelche Nachteile mit sich?

Man muss sich im klaren sein was wann passiert...

Beispiel für Queue aus dem Thread oder wie Günther es geschrieben hat ein Event dann die Daten von der anderen Seite aus holt...

Beispiel:

Ein Thread erzeugt permanent Daten und zwar nicht nur ein Integer sonst was kompliziertes...
Bei einem Syncronize musst du zwar warten auf den UI-Thread aber kannst sicher danach die Daten verwerfen...
Bei ein Queue Aufruf, musst Du sicher stellen, dass zum Zeitpunkt wo der UI-Thread die Daten darstellen will, die Daten auch noch vorhanden sind. Noch schwieriger ist es, wenn Du die Kontrolle - wie Günther - an ein anderes Programmteil übergeben hast... Denn falls der UI-Thread die Daten lesen will, muss sichergestellt werden, dass der Thread die Daten nicht gerade neu- oder überschreibt.

Musst Du alle Zwischenschritte immer darstellen? Ich habe hier z.B. eine Routine programmiert, die immer wenn der Thread neue Daten erzeugt und der UI-Thread die Daten noch nicht verarbeitet hat, der letzte Datensatz verworfen und sofort gegen den neuen Ausgetauscht wird.

Für Dein Problem, würde ich einfach 2 oder wie viele auch immer Du brauchst eigenen Threadsichere Queues erzeugen und diese über einen Workerthread der gemäß Priorität die einzelen Queues abarbeitet und dann problemlos Syncronisieren kann.

Mavarik

Der schöne Günther 29. Mär 2016 16:38

AW: TThread: Synchronize() priorisieren?
 
Zitat:

Zitat von Mavarik (Beitrag 1334072)
emm... nix anderes ist ein Syncronize...

Richtig, der benutzt auch TThread.Synchronize() oder .Queue() oder was auch immer. Es ging darum, den Thread nicht auf Gedeih und Verderb an irgendein Formular zu ketten sondern von außen festzulegen was geschehen soll. Vielleicht willst du auch nichts anzeigen, sondern nur loggen. Oder beides. Solche Anforderungen ändern sich ständig. Aber deshalb fasst man nicht ständig das Herzstück an.

Mavarik 29. Mär 2016 16:46

AW: TThread: Synchronize() priorisieren?
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1334075)
Zitat:

Zitat von Mavarik (Beitrag 1334072)
emm... nix anderes ist ein Syncronize...

Richtig, der benutzt auch TThread.Synchronize() oder .Queue() oder was auch immer. Es ging darum, den Thread nicht auf Gedeih und Verderb an irgendein Formular zu ketten sondern von außen festzulegen was geschehen soll. Vielleicht willst du auch nichts anzeigen, sondern nur loggen. Oder beides. Solche Anforderungen ändern sich ständig. Aber deshalb fasst man nicht ständig das Herzstück an.

Kommt darauf an, wenn der Thread der "einzige" ist der weiß was richtig ist, geht es nicht anders...
Aber ich verstehe deinen Einwand/Ansatz!

Medium 29. Mär 2016 19:25

AW: TThread: Synchronize() priorisieren?
 
Statt der Events via Methodenzeiger bevorzuge ich mittlerweile eine doppelt gepufferte Datenhaltung und Windows Messages. Beispiel:
Delphi-Quellcode:
// Threadklasse
type
  TMyThread = class(TThread)
  private
    FThreadBuffer: TObjectList; // Hier schreibt der Thread seine Daten rein
    FGUIBuffer: TObjectList; // Hier liest nachher die GUI raus
    FBufferCS: TCriticalSection;
    FForm: HWND;
    function GetItems: TObjectList;
    function GetItemCount: Integer;
  protected
    procedure Execute; override;
  public
    constructor Create(aForm: HWND);
    procedure LockData;
    procedure ReleaseData;
    property Items: TObjectList read GetItems;
    property ItemCount: Integer read GetItemCount;
  end;

const
  WM_MY_DATA_UPDATE = WM_USER + 1234;

implementation
 
constructor TMyThread.Create(aForm: HWND);
begin
  inherited(false);
  FForm := aForm;
  // Initialisierung der Buffer etc.
end;

procedure TMyThread.LockData;
begin
  FBufferCS.Enter;
end;

procedure TMyThread.ReleaseData;
begin
  FBufferCS.Leave;
end;

procedure TMyThread.GetItems: TObjectList;
begin
  FBufferCS.Enter;
  try
    result := FGUIBuffer;
  finally
    FBufferCS.Leave;
  end;
end;

procedure TMyThread.Execute;
begin
  repeat
    DoSlowStuffWith(FThreadBuffer);

    FBufferCS.Enter;
    try
      FGUIBuffer.Clear; // OwnsObjects!
      CloneAllItemsFromThreadBufferTo(FGUIBuffer);
    finally
      FBufferCS.Leave;
      PostMessage(FForm, WM_MY_DATA_UPDATE, 0, 0);
    end;
  until Terminated;
end;
Delphi-Quellcode:
// Formularklasse
type
  TMainForm = class(TForm)
  private
    procedure OnMyDataUpdate(var msg: TMessage); message WM_MY_DATA_UPDATE;
  public
  end;

implementation

procedure TMainForm.OnMyDataUpdate(var msg: TMessage);
begin
  myThreadInstance.LockData;
  try
    WriteItemsToGrid(myThreadInstance.Items);
  finally
    myThreadInstance.ReleaseData;
  end;
end;
Dabei ist es dann recht egal wie lange es dauert die Daten im Thread zusammenzustellen, die GUI muss maximal auf das Kopieren der ListItems warten, was in wenigen Millisekunden erledigt ist. Dadurch wird die GUI wunderbar flott, egal wie riesig z.B. die Tabelle ist aus der die Daten erst abgerufen werden müssen im Thread. Zudem muss der Thread nicht erst warten bis die GUI all ihren Krams ins Grid gepackt hat, der kann ja ruhig schon mal den nächsten Zyklus abrufen.

UND: Es ist dem Thread an sich völlig egal wer seine Daten abholt, wie der Abholer sie dann interpretiert, und ob überhaupt ein Abholer vorhanden ist.

Der Code ist natürlich nur eine ganz grobe pseudocodeähnliche Skizze, und je nach Anwendungsfall kommen natürlich auch mehrere verschiedene Buffer in Frage. Auch der Typ der Buffer ist an sich egal, wichtig ist nur, dass wirklich KOPIEN existieren, nicht nur Referenzen auf dieselben Instanzen in die der Thread rein schreibt. Auch ist ein Queue-ähnlicher Aufbau denkbar, bei dem erst Items in die Thread-Liste gefüllt werden, die dann auf einen Schlag in die GUI-Liste übertragen, und dann von der GUI abgeholt und gelöscht werden. Zentral ist aber die doppelte Datenhaltung um GUI und Thread nicht zu stark aufeinander warten zu lassen, und das Signaling via Windows Message. (Gerne packe ich auch noch die self-Referenz des sendenden Threads in wParam oder lParam, damit das Formular auch gleich den richtigen Absender an der Hand hat wenn es mehrere parallele gleichtypige Threads gibt.)

Delphi-Laie 29. Mär 2016 21:22

AW: TThread: Synchronize() priorisieren?
 
Zitat:

Zitat von romber (Beitrag 1334049)
Besteht die Möglichkeit, einen Thread so zu priorisieren, dass er immer Vorrang bei synchronisiertem Zugriff auf die GUI hat?

Das wurde hier ja schon verneint, ggf. bringt es Dich aber ein Stück weiter, diesen einen Thread wenigstens eine höhere Priorität zuzuweisen, als die anderen haben (ggf. auch zugewiesen bekamen, evtl. sogar absichtlich ein deutlich erniedrigte).

Generell ist Windows für Echtzeitanforderungen ungeeignet.

romber 4. Apr 2016 10:06

AW: TThread: Synchronize() priorisieren?
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1334063)
...Es kann allerdings passieren, daß bei Queue die Methode gar nicht abgearbeitet wird, da bei Freigabe der TThread-Instanz alle noch vorhandenen Queue-Events gelöscht werden. Dem kann man aber vorbeugen, in dem man anstatt der einfachen Queue-Methode die gleichnamige Klassenmethode mit dem zusätzlichen TThread-Parameter an erster Stelle verwendet und diesen als nil übergibt. Dann kann das zwar theoretisch immer noch passieren, aber nur wenn das ganze Programm vor Abarbeiten des Queue-Events beendet wird. Das wäre übrigens bei Synchronize auch der Fall.

Aber auch hier darf die mit Queue aufgerufene Methode nicht innerhalb des Threads deklariert werden, oder?

Mavarik 4. Apr 2016 10:09

AW: TThread: Synchronize() priorisieren?
 
Zitat:

Zitat von romber (Beitrag 1334575)
Zitat:

Zitat von Uwe Raabe (Beitrag 1334063)
...Es kann allerdings passieren, daß bei Queue die Methode gar nicht abgearbeitet wird, da bei Freigabe der TThread-Instanz alle noch vorhandenen Queue-Events gelöscht werden. Dem kann man aber vorbeugen, in dem man anstatt der einfachen Queue-Methode die gleichnamige Klassenmethode mit dem zusätzlichen TThread-Parameter an erster Stelle verwendet und diesen als nil übergibt. Dann kann das zwar theoretisch immer noch passieren, aber nur wenn das ganze Programm vor Abarbeiten des Queue-Events beendet wird. Das wäre übrigens bei Synchronize auch der Fall.

Aber auch hier darf die mit Queue aufgerufene Methode nicht innerhalb des Threads deklariert werden, oder?

Doch!

romber 4. Apr 2016 10:21

AW: TThread: Synchronize() priorisieren?
 
Zitat:

Zitat von Mavarik (Beitrag 1334577)
Doch!

Bei mir werden die Threads automatisch freigegeben. Sollte der Thread zum Zeitpunkt des Aufrufs der Queue-Methode nicht mehr vorhanden sein, was wird denn aufgerufen?

Uwe Raabe 4. Apr 2016 10:23

AW: TThread: Synchronize() priorisieren?
 
Zitat:

Zitat von romber (Beitrag 1334580)
Bei mir werden die Threads automatisch freigegeben. Sollte der Thread zum Zeitpunkt des Aufrufs der Queue-Methode nicht mehr vorhanden sein, wie soll die Thread-Methode von Haupthread aufgerufen werden?

Deswegen sollst du ja auch nil als Parameter übergeben. Dann überlebt das Queue auch das Ende des Threads. Die übergebene Methode ist ja eine Anonyme Methode. Obwohl sie in deiner Thread-Methode deklariert wird, gehört sie damit nicht wirklich zu der Thread-Instanz.

romber 4. Apr 2016 10:30

AW: TThread: Synchronize() priorisieren?
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1334581)
Deswegen sollst du ja auch nil als Parameter übergeben. Dann überlebt das Queue auch das Ende des Threads. Die übergebene Methode ist ja eine Anonyme Methode. Obwohl sie in deiner Thread-Methode deklariert wird, gehört sie damit nicht wirklich zu der Thread-Instanz.

Ok, also doch als anonyme Methode. Etwa so?

Delphi-Quellcode:
TThread.Queue(nil, procedure()
                   begin
                     MachWas;
                   end);
Was ich gemeint habe, ist dass die Methode nicht als Teil des Threads deklariert werden darf:

Delphi-Quellcode:
type
  TMeinThread = class(TThread)
  //...
  protected
    procedure Execute; override;
    procedure DoDataChanged;
end;

procedure TMeinThread.Execute;
begin
  //..
  TThread.Queue(nil, DoDataChanged);
end;

procedure TMeinThread.DoDataChanged;
begin
  myCustomDataSource.DataChanged;
end;

Uwe Raabe 4. Apr 2016 10:47

AW: TThread: Synchronize() priorisieren?
 
Zitat:

Zitat von romber (Beitrag 1334584)
Ok, also doch als anonyme Methode. Etwa so?

Delphi-Quellcode:
TThread.Queue(nil, procedure()
                   begin
                     MachWas;
                   end);

Genau so! Allerdings darf MachWas dann auch nicht voraussetzen, daß die Thread-Instanz noch existiert, wenn du das nicht sicher stellen kannst.

romber 4. Apr 2016 10:50

AW: TThread: Synchronize() priorisieren?
 
Perfekt, vielen Dank!


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