AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Algorithmen, Datenstrukturen und Klassendesign Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property
Thema durchsuchen
Ansicht
Themen-Optionen

Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property

Ein Thema von norwegen60 · begonnen am 7. Nov 2019 · letzter Beitrag vom 18. Nov 2019
Antwort Antwort
norwegen60

Registriert seit: 23. Dez 2007
Ort: Schwarzwald
529 Beiträge
 
Delphi 12 Athens
 
#1

AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property

  Alt 16. Nov 2019, 09:32
Danke noch für das Feedback. Dass TMultiReadExclusiveWriteSynchronizer nur bei vielen Lesevorgängen wirklich Sinn macht, war mir bewusst.
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:
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.
Das Ziel ist:
  • Über den Main-Thread soll eine Liste gefüllt werden. Das kann sehr schnell passieren (In meinem Fall Kommandos an ein Gerät. Die Liste dient als Puffer)
  • Die Antwort wird in FWert geschrieben. Der Wert wird bei jeder Änderung mit dem Main synchronisiert.
  • Zusätzlich soll der Main die Möglichkeit haben schreibend und lesend auf FWert zuzugreifen

Meine Fragen:
  1. Ist der Zugriff auf die Liste sauber geschützt?
  2. Ist der Zugriff auf FWert sauber implementiert?
  3. Welche Möglichkeiten gibt es, dass der SyncWert zum Main zu keiner Verzögerung führt auch wenn die Abarbeitung im Main länger dauert? (Im Moment löse ich es, dass FWert auf der Main-Seite in eine Liste geschrieben wird, und die abgearbeitet wird)
  4. Ist es richtig, dass in SyncEventWert nur auf FWert zugegriffen werden darf (nicht auf Wert)? Ich meine durch den Aufruf von Synchronize(SyncEventWert) wird schon ein Schutz erzeugt und wenn ich jetzt auf Wert zugreife, kommt der zusätzliche Section-Schutz um GetWert zum Tragen.
  5. Wird bei mehrfachem Aufruf von TSection.Acquire erkannt, wenn der aus demselben Thread geschieht oder führt auch das zu einer Blockade?
  6. Ist an meinem Code sonst was falsch?
Zum einfacheren anschauen habe ich den kompletten Beispiel-Code incl. Main-Form als Zip angehängt.
  • Durch Klick auf [Write into List] werden Einträge in die Liste geschrieben. Durch schnelles Klicken sieht man, dass Liste langsamer abgearbeitet wird.
  • Durch Klick auf [Write into Wert] wird direkt in FWert geschrieben und auch sofort wieder zurück gegeben
  • Durch Klick auf [Read Wert] wird FWert direkt gelesen
Vielen Dank
Gerd
Angehängte Dateien
Dateityp: zip ThrTest.zip (57,3 KB, 6x aufgerufen)
  Mit Zitat antworten Zitat
HolgerX

Registriert seit: 10. Apr 2006
Ort: Leverkusen
989 Beiträge
 
Delphi 6 Professional
 
#2

AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property

  Alt 17. Nov 2019, 06:20
Hmm..


Delphi-Quellcode:

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]; // ---->> Zugriff auf die Property 'Wert' und somit auf den Setter 'SetWert'
        FListe.Delete(0);
      end;
    finally
      FSection.Release; // Zugriff auf FListe wieder freigeben
    end;
    TThread.Sleep(300);
  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;

Der DeadLock kommt hier deshalb, weil Du innerhalb des Locks im Execute auf den Setter von Wert zugreifst, welcher selber wieder ein Lock machen will...

Dein Synchronize sollte auch nicht im Setter, sondern im Execute gemacht werden..

Delphi-Quellcode:

procedure TthrTest.Execute;
var
  tmpDataToSend : String;
begin
  inherited;

  FWert := '';

  while not Terminated do
  begin
    FSection.Acquire; // Zugriff auf FListe schützen
    try
      if FListe.Count > 0 then
      begin
        FWert := FListe[0];
        FListe.Delete(0);
      end;
    finally
      FSection.Release; // Zugriff auf FListe wieder freigeben
    end;


    // Nicht innerhalb der CriticalSection da Main sonst blockieren könnte
    if FWert <> 'then begin
      Synchronize(SyncEventWert);
      FWert := '';
    end;

    TThread.Sleep(1);
  end;
end;

Deine Property Wert mit seinem Setter ist somit Überflüssig,ja sogar falsch, da deine Liste über 'SendToListe' gefüllt wird und auf FWert nur innerhalb von Execute zugegriffen werden sollte, da dieser nur für Synchronize benötigt wird.

Für den Sleep genügt auch ein Wert von einer MS...
(Ja ich Verwende Delphi 6 Pro und will NICHT wechseln!)
  Mit Zitat antworten Zitat
norwegen60

Registriert seit: 23. Dez 2007
Ort: Schwarzwald
529 Beiträge
 
Delphi 12 Athens
 
#3

AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property

  Alt 17. Nov 2019, 13:44
Es ist immer das selbe Problem. Wenn man seinen ganzen Sourcecode ausgeben würde, würde der Code zu kompliziert. Reduziert man ihn auf das (vermeintlich) Wesentliche, sieht der Beispielcode kompliziert aus.

In meinem Fall wird Wert in Wirklichkeit von der RS232 zurück geliefert, über AsyncPro.ComPortTriggerAvail erfasst und dort auch Wert gesetzt. D.h. Exceute bekommt davon gar nichts mit. Deshalb das Synchronize im Setter. Mit deiner Änderung würde Execute nichts mitbekommen wenn ich den Wert über den Button [Write into Wert] vom Main her ändere. Das würde dein Execute nicht erkennen und somit auch nichts an den Main zurück liefern. (In dem Fall simuliert der Button [Write into Wert] den PortTrigger.)

Mit dem Doppel-Lock in meinem Execute-Beispiel hast du recht. Tritt aus vorigen Gründen so im Echtcode aber nicht auf.
Das seltsame: Trotz des Doppellock im Exceute läuft mein ZIP-Beispiel ohne Probleme. Nur der Echtcode nicht. Ich bin dabei den Beispielcode mehr und mehr an meine Realcode ranzubringen.

Den Sleep habe ich so lang gemacht um simulieren zu können, dass sich die Liste auch mal schneller füllt als sie abgearbeitet wird und trotzdem kein Befehl verliert.

Darf Synchronize wirklich nur in Exceute aufgerufen werden
  Mit Zitat antworten Zitat
HolgerX

Registriert seit: 10. Apr 2006
Ort: Leverkusen
989 Beiträge
 
Delphi 6 Professional
 
#4

AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property

  Alt 17. Nov 2019, 20:43
Hmm..

Wenn 'Wert' eh nur direkt gesetzt wird, dann kannst Du auch deinen kompletten Thread wegschmeißen!

Nur wenn dein AsyncPro.ComPortTriggerAvail das 'SendToListe' verwendet, kann die Liste überhaupt funktionieren..
Und nur dann könntest Du den Thread brauchen...
(Ja ich Verwende Delphi 6 Pro und will NICHT wechseln!)
  Mit Zitat antworten Zitat
norwegen60

Registriert seit: 23. Dez 2007
Ort: Schwarzwald
529 Beiträge
 
Delphi 12 Athens
 
#5

AW: Thread CriticalSection bzw TMultiReadExclusiveWriteSynchronizer pro Property

  Alt 18. Nov 2019, 00:16
Noch mal: In meinem Beispiel wollte ich die Situation simulieren, dass zwei Threads auf ein und denselben Wert zugreifen. Deshalb wird dieser Wert über Exceute aus der Liste gefüllt als auch über einen Button-Klick aus dem Main-Thread. Dies nur um zu testen, ob ich Konflikte erzeugen kann

Im realen Code sendet der Main Befehle in eine Liste des Thread. Diese Befehle werden in Execute aus der Liste an den ComPort geschickt. Ein neuer Befehl wird aber erst geschickt, wenn der vorherige geantwortet hat. Deshalb das Puffern in der Liste.
Irgendwann antwortet das angeschlossene Gerät und die Antwort wird über das Event ComPortTriggerAvailable so lange gesammelt bis sie komplette ist. Dann wird sie in Wert geschrieben und damit über den Setter an den Main geschickt. Danach kann Executen den nächsten Befehl aus der Lsite auslesen und an den ComPort schicken. Zwischenzeitlich kann der Main aber schon weitere Befehle an die Liste geschickt werden.
Auch wenn der Wert über Synchronize an den Main übergeben wurde, soll es möglich sein, diesen Wert auch manuell abzufrage ohne dass es kracht.

Geändert von norwegen60 (18. Nov 2019 um 00:20 Uhr)
  Mit Zitat antworten Zitat
Antwort Antwort

 

Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 04:55 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