Einzelnen Beitrag anzeigen

Der schöne Günther

Registriert seit: 6. Mär 2013
6.176 Beiträge
 
Delphi 10 Seattle Enterprise
 
#3

AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property

  Alt 7. Nov 2019, 12:30
Das sind im Endeffekt ja viele verschiedene Fragen auf einmal.

Ich denke bei den wenigstens Anwendungen ist das wirklich interessant ob im Hintergrund eine TCriticalSection, ein TMonitor, ein TMultiReadSonstwas zum Einsatz kommt. Die paar Nanosekunden sparen wir uns an dieser Stelle am besten vorerst...

Ich finde das ziemlich kompliziert gelöst. Erstens finde ich es fehl am Platz dass der Thread sich darum kümmern muss hier etwas in den Hauptthread zu pushen. Der Thread kann ein Event anbieten, das Formular registriert sich dort. Wenn das Formular es braucht dass es im Hauptthread ausgeführt wird, macht es das bitte selbst.

Wie du schon bemerkt hast ist ein Riesenproblem dass der Event-Handler ausgeführt wird während der Zugriff noch gesperrt ist. Wenn dein Formular jetzt z.B. den Wert noch einmal lesen will hängt alles - Weil deine TMyThread-Instanz hat es noch gesperrt. Folglich:
  1. Zugriff sperren
  2. Wert setzen, merken ob vorher anders war
  3. Sperre aufheben
  4. Ist der Wert vorher anders gewesen? Change-Event auslösen

Zweitens würde ich mir verschiedene Events sparen - Vielleicht ist bei mir die Welt viel einfacher, aber in 90 % der Fälle will man wirklich nur darauf reagieren dass sich etwas geändert hat und pinselt dann die Oberfläche neu. Sicher könnte man etwas Compiler-Strom sparen das alles haarklein aufzuteilen, aber im ersten Wurf braucht man das sicher nicht.

Eine kleine Änderung die ich mir noch erlaubt habe ist dass ich dem Thread nicht zwei oder mehr Werte direkt geben würde - Lager die Werte nochmal in eine einfache Struktur aus, einen Record (also Wertetyp).

Das sähe am Schluss dann so aus:

Delphi-Quellcode:
unit Unit2;

interface uses
   System.Classes,
   System.SyncObjs;

type
   TData = record
      Prop1:   Real;
      Prop2:   Real;
    end;

   TDataChangeEventHandler = procedure(const data: TData) of object;

   TMyThread = class(TThread)
      private var
         handler:   TDataChangeEventHandler;
         FCount:      Integer;
      private
         procedure incCounter(); inline;
      protected var
         data: TData;
         mutex: TCriticalSection;
      protected
         procedure Execute(); override;
         procedure setProp1(const value: Real);
         procedure setProp2(const value: Real);
         procedure invokeChangeEvent(); inline;
      public
         constructor Create(CreateSuspended: Boolean);
         destructor Destroy(); override;

         procedure setOnChange(const handler: TDataChangeEventHandler);
         function getOnChange(): TDataChangeEventHandler;
         function getData(): TData;
   end;

implementation uses System.SysUtils;

{ TMyThread }

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

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

procedure TMyThread.Execute();
begin
   while (not Terminated) do
      begin
         if Odd(FCount) then
            setProp1(FCount)
         else
            setProp2(FCount);

         incCounter();
         TThread.Sleep(50);
      end;
end;

function TMyThread.getData(): TData;
begin
   mutex.Acquire();
   try
      Result := data;
   finally
      mutex.Release();
    end;
end;

function TMyThread.getOnChange(): TDataChangeEventHandler;
begin
   mutex.Acquire();
   try
      Result := handler;
   finally
      mutex.Release();
    end;
end;

procedure TMyThread.incCounter();
begin
   if (FCount < Integer.MaxValue) then
      Inc(FCount)
   else
      FCount := Integer.MinValue;
end;

procedure TMyThread.invokeChangeEvent();
var
   _handler: TDataChangeEventHandler;
begin
   _handler := getOnChange();
   if Assigned(_handler) then
      _handler(data);
end;

procedure TMyThread.setOnChange(const handler: TDataChangeEventHandler);
begin
   mutex.Acquire();
   try
      self.handler := handler;
   finally
      mutex.Release();
   end;
end;

procedure TMyThread.setProp1(const value: Real);
var
   hasChanged: Boolean;
begin
   mutex.Acquire();
   try
      hasChanged := (data.Prop1 <> value);
      if hasChanged then
         data.Prop1 := value;
   finally
      mutex.Release();
   end;

   if hasChanged then
      invokeChangeEvent();
end;

procedure TMyThread.setProp2(const value: Real);
var
   hasChanged: Boolean;
begin
   mutex.Acquire();
   try
      hasChanged := (data.Prop2 <> value);
      if hasChanged then
         data.Prop2 := value;
   finally
      mutex.Release();
   end;

   if hasChanged then
      invokeChangeEvent();
end;

end.
  Mit Zitat antworten Zitat