![]() |
Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property
Hallo zusammen,
ich bin noch nicht ganz ThreadSave arbeite aber daran. Nach dem Lesen einiger Themen habe ich noch eine Verständnisfrage. Schützt eine CriticalSection den kompletten Thread oder kann die CS auch auf den Zugriff auf eine Property runter gebrochen werden? In etwa so:
Delphi-Quellcode:
Im Main-Thread sollte nun Event per Syncchronize oder Zugriff auf Property möglich sein
type
TMyThread = class(TThread) private FCount:Integer; FProp1: Real; FProp2: Real; FEventProp1: TThreadReal; FEventProp2: TThreadReal; FSectionProp1: TCriticalSection; // CriticalSection für Prop1 FSectionProp2: TCriticalSection; // CriticalSection für Prop2 procedure SyncEventProp1; procedure SyncEventProp2; function GetProp1: Real; function GetProp2: Real; procedure SetProp1(const Value: Real); procedure SetProp2(const Value: Real); protected procedure Execute; override; public property Prop1: Real read GetProp1 write SetProp1; property Prop2: Real read GetProp2 write SetProp2; property EventProp1: TThreadReal read FEventProp1 write FEventProp1; property EventProp2: TThreadReal read FEventProp2 write FEventProp2; constructor Create(sEinst: String); destructor Destroy; override; end; implementation constructor TMyThread.Create(sEinst: String); begin inherited Create(true); FSectionProp1 := TCriticalSection.Create; FSectionProp2 := TCriticalSection.Create; FCount := 0; end; destructor TMyThread.Destroy; begin FSectionProp1.Free; FSectionProp2.Free; inherited Destroy; end; procedure TMyThread.Execute; begin inherited; while not Terminated do begin if Odd(FCount) then Prop1 := FCount else Prop2 := FCaount; FCount := FCount + 1; Sleep(50); end; end; function TMyThread.GetProp1: Real; begin FSectionProp1.Acquire; try Result := FProp1; finally FSectionCount.Release; end; end; function TMyThread.GetProp1: Real; begin FSectionProp1.Acquire; try Result := FProp1; finally FSectionCount.Release; end; end; procedure TMyThread.SetProp1(const Value: Real); begin FSectionProp1.Acquire; try if FProp1 <> Value then // Hier bewusst FProp1 und nicht Prop1, da Property ja schon geschützt begin FProp1 := Value; Synchronize(SyncEventProp1); // Auch das in CS und damit Ausgabe geschützt end; finally FSectionProp1.Release; end; end; procedure TMyThread.SetProp2(const Value: Real); begin FSectionProp2.Acquire; try if FProp2 <> Value then // Hier bewusst FProp2 und nicht Prop2, da Property ja schon geschützt begin FProp2 := Value; Synchronize(SyncEventProp2); // Auch das in CS und damit Ausgabe geschützt end; finally FSectionProp2.Release; end; end; procedure TMyThread.SyncEventProp1; begin if Assigned(FEventProp1) then FEventConnected(FProp1); // Hier bewusst FProp1 und nicht Prop1, da Property ja schon durch SetProp1 geschützt und Sync nur von dort geschieht end; procedure TMyThread.SyncEventProp2; begin if Assigned(FEventProp2) then FEventConnected(FProp2); // Hier bewusst FProp1 und nicht Prop1, da Property ja schon durch SetProp1 geschützt und Sync nur von dort geschieht end; end.
Delphi-Quellcode:
Damit könnte theoretisch jemand auf Prop1 zugreifen währen der Thread in Prop2 schreibt
procedure TMain.FormCreate(Sender: TObject);
begin MyThread.EventProp1 := EventProp1; end; procedure TMain.EventProp1(Value: Real); // Ständige Aktualisierung begin Label1.Caption := format('%3.2f', [Value]); end; procedure TMain.Button1Click((Sender: TObject); begin Label2.Caption := format('%3.2f', [MyThread.Prop1]); Label3.Caption := format('%3.2f', [MyThread.Prop2]); end; Ich habe hier bewusst eine Mischung aus Sychronice und Aufruf der Variablen gewählt um auch zu erfahren ob diese gemischte Verwendung Threadsafe implementiert wäre. Bisher habe ich CS verwendet, habe aber beim erneuten Suchen gesehen, dass TMultiReadExclusiveWriteSynchronizer vielleicht besser wäre. Frage nach unterschiedlichen Blöcken gilt deshalb analog für TMultiReadExclusiveWriteSynchronizer (msProp1, msProp2) Danke für euer Feedback Gerd |
AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property
TMultiReadExclusiveWriteSynchronizer würde ich da machen wo wirklich mehr gelesen and geschrieben wird.
Aber Vorsicht wenn du FMX einsetzt: Unter FMX-Mobile wird das nur als normaler TMonitor implementiert (also keine MultiRead...). Müsste ich jetzt nachsehen ob Android/iOS oder Beide. |
AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property
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:
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 ![]() 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. |
AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property
Vielen Dank für das schnelle Feedback. Ein paar Dinge habe ich schon umgesetzt.
Vielleicht kurz beschrieben, an was ich gerade dran bin. Universelle ComPort-Ansteuerung die je nach Gerät ein oder zwei Befehle automatisch (pollend) schickt und zusätzlich die Möglichkeit hat, zusätzliche Befehle vom Main entgegen zu nehmen, zu senden und Antwort zurück zugeben. Da die Befehle immer seriell gesendet werden, kam das mit zwei Events für Prop1 (z.B. Position) und Prop2 (z.B. Speed) auf. Z.B. wird in einer Anwendung aufgrund der Position ein Motor abgeschalten. => Position wird öfter abgefragt als Speed und im Event des Mains entsprechend ausgewertet. Ist aber kein Killer für Rückgabe als Record. Hier kurz zusammengefasst wie ich es verstanden habe
Die Bemerkung Zitat:
Zitat:
Wo ist bei mir die Sicherheitslücke? |
AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property
Und noch eine Frage hierzu
Delphi-Quellcode:
Warum der Zwischenschritt über _handler? Warum nicht so?
procedure TMyThread.invokeChangeEvent();
var _handler: TDataChangeEventHandler; begin _handler := getOnChange(); if Assigned(_handler) then _handler(data); end;
Delphi-Quellcode:
Gewohnheit oder begründet?
procedure TMyThread.invokeChangeEvent();
begin if Assigned(getOnChange) then getOnChange(data); end; |
AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property
Weil es hochtheoretisch sein könnte dass jemand in der Zwischenzeit zwischen "if" und "then" aus einem anderen Thread setOnChange(..) aufruft und einen anderen Handler (oder garkeinen) setzt. Dann würdest du im "then"-Teil einen leeren Funktionszeiger aufrufen wollen und es gäbe eine Zugriffsverletzung.
Könnte man das OnChange nicht mittendrin ändern (z.B. würde es nur einmal im Konstruktor des Threads übergeben werden) müsste man sich davor nicht fürchten und man bräuchte es nicht und könnte einfach sagen
Delphi-Quellcode:
if Assigned(getOnChange) then
getOnChange(data); |
AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property
Zitat:
Delphi-Quellcode:
wird also
Sperre.Betreten()
try wert = neuer wert finally Sperre.Verlassen() end;
Delphi-Quellcode:
Und dein "OnChange" ist gleichzeitig auch noch dass wir mit
Sperre.Betreten()
try wert = neuer wert Wenn der Wert sich geändert hat: Führe OnChange aus finally Sperre.Verlassen() end;
Delphi-Quellcode:
hantieren (davon hatte ich ja stark abgeraten).
TThread.Synchronize(..)
Dann kann es sein dass du in deiner Ereignisbehandlung (also auf deinem Formular) einen Getter oder Setter vom Thread aufrufst. Niemand kann dich daran hindern. Nur was passiert dann? Er will wieder sperren. Und darauf wird er ewig warten, denn der Thread hält es noch gesperrt und wartet darauf dass der
Delphi-Quellcode:
fertig wird. Der wird aber nie fertig, da er auf die Sperre wartet. Eine Verklemmung auf ewig.
TThread.Synchronize(..)
Das meinte ich. Weiterhin ist es denke ich unnötig dass der Thread so lange pausiert und wartet bis jemand darauf reagiert hat. Deine serielle Kommunikation würde nun so lange stehen bis das Gepinsel auf dem Formular fertig ist. |
AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property
OK, weitestgehend verstanden (Hoffe ich)
Rein theoretisch und vor allem für mein Verständnis:
Delphi-Quellcode:
procedure TMyThread.invokeChangeEvent();
var _handler: TDataChangeEventHandler; begin _handler := getOnChange(); if Assigned(_handler) then _handler(data); end; |
AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property
Zu Punkt 3 & 4: Da hast du Recht. Ich hatte nur das Lesen auf dem Schirm, aber hochtheoretisch kann jemand setProp1(..) aufrufen, und vor der letzten Zeile wird der Thread angehalten und von einem anderen wird setProp2(..) aufgerufen. Der läuft komplett durch und später wird setProp1(..) noch ordentlich zu Ende gebracht. Keine Datenkorruption, aber das OnChange-Event wurde dann einfach zwei mal mit den neuen Werten aufgerufen. Zitat:
|
AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property
Liste der Anhänge anzeigen (Anzahl: 1)
Danke noch für das Feedback. Dass
Delphi-Quellcode:
nur bei vielen Lesevorgängen wirklich Sinn macht, war mir bewusst.
TMultiReadExclusiveWriteSynchronizer
Der kurze Sleep kommt daher, dass eingehende Befehle schnell abgearbeitet werden sollen, was aber auch über Event lösbar wäre Ich habe jetzt meine Anwendung so, dass sie grundsätzlich funktioniert. Wenn ich aber gewisse Funktionen aktiviere, bekomme ich Deadlocks und weiß nicht so genau warum. Deshalb habe ich doch noch mal was grundsätzliches. Zum leichteren Nachvollziehen habe ich ein ein kleines Beispiel konstruiert:
Delphi-Quellcode:
Das Ziel ist:
unit uThrTest;
interface uses Classes, SyncObjs; type TThreadString = procedure(Value: String) of object; TthrTest = class(TThread) private FWert: String; FListe: TStringList; FSyncWert: TThreadString; procedure SyncEventWert; function GetWert: String; procedure SetWert(const Value: String); protected FSection: TCriticalSection; procedure Execute; override; public property Wert: String read GetWert write SetWert; property SyncWert: TThreadString read FSyncWert write FSyncWert; constructor Create(CreateSuspended: Boolean); destructor Destroy; override; procedure SendToListe(sValue: String); end; implementation //****************************************************************************************************************************************** { TthrProperty } //****************************************************************************************************************************************** constructor TthrTest.Create(CreateSuspended: Boolean); //****************************************************************************************************************************************** begin inherited Create(CreateSuspended); FSection := TCriticalSection.Create; Wert := ''; FListe := TStringList.Create; end; destructor TthrTest.Destroy; //****************************************************************************************************************************************** begin Terminate; WaitFor; FListe.Free; FSection.Free; end; procedure TthrTest.Execute; //****************************************************************************************************************************************** begin inherited; while not Terminated do begin FSection.Acquire; // Zugriff auf FListe schützen try if FListe.Count > 0 then begin Wert := FListe[0]; FListe.Delete(0); end; finally FSection.Release; // Zugriff auf FListe wieder freigeben end; TThread.Sleep(300); end; end; procedure TthrTest.SendToListe(sValue: String); //****************************************************************************************************************************************** begin if (sValue <> '') then begin FSection.Acquire; // Zugriff auf FListe schützen try FListe.Add(sValue); finally FSection.Release; // Zugriff auf FListe wieder freigeben end; end; end; function TthrTest.GetWert: String; //****************************************************************************************************************************************** begin FSection.Acquire; // Zugriff auf FWert schützen try Result := FWert; finally FSection.Release; // Zugriff auf FWert wieder freigeben end; end; procedure TthrTest.SetWert(const Value: String); //****************************************************************************************************************************************** var bChanged: Boolean; begin FSection.Acquire; // Zugriff auf FWert schützen try bChanged := (FWert <> Value); if bChanged then FWert := Value; finally FSection.Release; // Zugriff auf FWert wieder freigeben end; // Nicht innerhalb der CriticalSection da Main sonst blockieren könnte if bChanged then Synchronize(SyncEventWert); end; procedure TthrTest.SyncEventWert; //****************************************************************************************************************************************** begin if Assigned(FSyncWert) then FSyncWert(FWert); // Zugriff auf FWert da durch Synchronize schon Schutz erzeugt wird end; end.
Meine Fragen:
Gerd |
Alle Zeitangaben in WEZ +1. Es ist jetzt 10:07 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