Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   Delphi TThreadList benutzen um Variablen im Hauptthread zu schützen (https://www.delphipraxis.net/114195-tthreadlist-benutzen-um-variablen-im-hauptthread-zu-schuetzen.html)

Frankieboy82 21. Mai 2008 09:20


TThreadList benutzen um Variablen im Hauptthread zu schützen
 
Hallo,

ich hab da ein ziemliches Problem mit der Synchronisation zweier Threads...
Da es sich bei dem Sourcecode um eine Unit mit einer visuellen Komponente handelt, geht es einmal um den Programm-(oder VCL-)Thread und dann einen zweiten "Arbeits"thread, der von der visuellen Komponente genutzt wird.

Hier mal eine beispielhafte - stark verkürzte - Version:
Delphi-Quellcode:
type
 
  [..]
 
  TWorkThread = class(TThread) //Der Arbeitsthread
  private
    FOwner: TBeispielVC;
    [..]
  protected
    procedure FetchInput_SHARED;
    procedure SendOutput_SHARED;
    [..]
  public
    [..]
    constructor Create( AOwner: TDosCommand );
  end;

TBeispielVC = class(TComponent) //Die visuelle Komponente, die man dem Formular hinzufügt
  private
    [..]
    { Achtung! Die unteren beiden Variablen ("SHARED") werden von beiden Threads
      gleichzeitig benutzt (Datenaustausch). Das Lesen/Ändern (egal von welchem Thread aus)
     ergibt zum Teil Zugriffsfehler! }
    FInputLines_SHARED: TStringList;
    FOutputLines_SHARED: TStrings;
    [..]
    FSync: TMultiReadExclusiveWriteSynchronizer;
  protected
  public
    constructor Create(AOwner: TComponent); override;                //Create-Konstruktor
    destructor Destroy; override;                                    //Destroy-Desktruktor
    procedure Execute;                                               //Methode um die Verarbeitung zu starten
    procedure Stop;                                                  //Stoppt (beendet!) die Verarbeitung
    [..]
  end;
Kurz zur Erlätuerung des des Ganzen:
TBeispielVC ist die visuelle Komponente, die man einem Forumlar hinzufügt. Sie fungiert eigentlich nur als "Einstellungsspeicher" und übernimmt die Steuerbefehle wie Execute (Verarbeitung starten) oder Stop "Verarbeitung abbrechen" usw...
Die visuelle Komponente gehört logischerweise zum Hauptthread der eigentlichen Delphi-Anwendung.

Die wirkliche Verarbeitung übernimmt ein zusätzlich generierter Thread: TWorkThread. Der Hauptcode dieses Threads ist eine repeat until Schleife, die so lange läuft, bis das ganze vom Benutzer über den VCL-Thread (z.B. mit TBeispielVC.Stop) beendet wird.
-> Das Problem ist der Datenaustausch zwischen den beiden Threads.

TBeispielVC legt die zu übernehmenden Daten in der StringList FInputLines_SHARED (in ihrer eigenen Struktur) ab.
Der Arbeitsthread liest sie dann irgendwann mit der prozedur FetchInput_SHARED aus (wenn er "kann"), kopiert sie in einen eigenen TStringList Puffer und leert dann FInputLines_SHARED - So weiß die VC, dass die Daten übernommen wurden.
Nach dem Verarbeitungsprozess kopiert der Workthread den Output aus einer eigenen TStrings-Liste in die VC-TStrings-Liste FOutputLines_SHARED - per prozedur SendOutput_SHARED. Nach erfolgreichem Abschluss löscht er die Daten in seinem "eigenen" Puffer.
Das passiert wieder und wieder...

Man sieht also: Nur der Workthread liest und schreibt im VCL-Thread. Der VCL-Thread selber wartet nur aufs Abholen oder Anliefern der Daten, hat aber mit der Workthread-Struktur nichts zu tun.

Deswegen/Trotzdem kriege ich laufend Zugriffsverletzungen. Ich habe versucht, den Speicherbereich von TBeispielVC durch einnen MultiWreadExclusiveWriteSynchronizer zu schützen (Variable FSync) - ohne viel Erfolg.
Jedesmal, wenn der Workthread in Input_SHARED oder Output_SHARED liest/schreibt, ruft er vorher FOwner.FSync.BeginRead und danach FOwner.FSync.EndRead (oder BeginWrite und EndWrite) auf, aum eine Kollision zu vermeiden.
Auch wenn TBeispielVC die eigenen beiden Variablen Input_SHARED oder Output_SHARED ändert, wird FSync benutzt.
Ich dachte eigentlich, dass sei die Lösung, aber ich habe immer noch schwerste Zugriffsfehler (Absturz von Delphi)!

Jetzt will ich eine TThreadList benutzen, um die beiden Austausch-Variablen in der Struktur von TBeispielVC zu schützen.
Leider habe ich noch nie mit einer solchen Liste gearbeitet und bräuchte etwas Hilfe:
Wo und wie lege ich die ThreadList an und wie schütze ich damit die SHARED-Variablen? Außerdem muss ich ja noch TThreadList.LockList vom TWorkthread aus aufrufen, wenn von daraus in die beiden besagten Variablen geschrieben oder daraus gelesen wird...

Wenn jemand Erfahrung mit sowas hat, wär ne Hilfe sehr nett!


Grüße,

Frank

spaxxn 21. Mai 2008 09:36

Re: TThreadList benutzen um Variablen im Hauptthread zu schü
 
Im Zusammenhang mit TThreadList, befass dich mal mit CriticalSection's

Die TThreadList ist nichts anderes als eine TList, wo eben mit CriticalSection gearbeitet wird.

sirius 21. Mai 2008 09:38

Re: TThreadList benutzen um Variablen im Hauptthread zu schü
 
Zitat:

Zitat von spaxxn
Im Zusammenhang mit TThreadList, befass dich mal mit CriticalSection's

Die TThreadList ist nichts anderes als eine TList, wo eben mit CriticalSection gearbeitet wird.

Aber Synchronistation betreibt er ja laut eigener Aussage über TMultiReadExclusiveWriteSynchronizer.

Edit: Für mich stellt sich die Frage, in wie weit die Strings der StringList wirklich kopiert werden. Es wird vielleicht nur der Referenzzähler erhöht. Und was bedeutet das für den Zugriff?

spaxxn 21. Mai 2008 09:43

Re: TThreadList benutzen um Variablen im Hauptthread zu schü
 
Kannst du mal die implementation posten?

@sirius: Ich wollte nur verdeutlichen, dass ihm das reine Verwenden von TThreadList zusätzlich das Verwalten von Strings mit ins Haus bringt :P Da würde das Verwenden der bereits vorhandenen Listen in Verbindung mit CriticalSection's schneller gehen.

Edit: Rechtschreibfehler und so :P

Frankieboy82 21. Mai 2008 09:54

Re: TThreadList benutzen um Variablen im Hauptthread zu schü
 
OK, hier mal die Implementation der beiden wichtigen Prozeduren aus TWorkThread:

Delphi-Quellcode:
procedure TWorkThread.FetchInput_SHARED;
begin
  if FOwner.FInputLines_SHARED.Count > 0 then
  begin
    FInputLines_SHARED.Assign(FOwner.FInputLines_SHARED);
    FOwner.FInputLines_SHARED.Clear;
  end;
end;
Wie man sieht, passiert nicht viel: Enthält der Puffer der VC Strings, dann werden sie übernommen und danach gelöscht.


Delphi-Quellcode:
procedure TWorkThread.SendOutput_SHARED;
begin
  if Assigned(FOwner.FOutputLines_SHARED) then
      begin
        FOwner.FOutputLines_SHARED.BeginUpdate;
        if FOwner.FOutputLines_SHARED.Count = 0 then
          begin
            if (FOutputType = otEntireLine) then
              FOwner.FOutputLines_SHARED.Add(FOutputStr)
            else
              FOwner.FOutputLines_SHARED.Text := FOutputStr;
          end
        else
          begin
            // change the way to add by last addstring type
            if FLineBeginned then
              FOwner.FOutputLines_SHARED[FOwner.FOutputLines_SHARED.Count - 1] := FOutputStr
            else
              FOwner.FOutputLines_SHARED.Add(FOutputStr);
          end;
        FOwner.FOutputLines_SHARED.EndUpdate;
      end;
  FLineBeginned := (FOutputType = otBeginningOfLine);
end;
Das hier ist etwas komplizierter, ich habe es aus einem anderen Quelltext übernommen, und muss es noch vereinfachen.

Frankieboy82 21. Mai 2008 09:57

Re: TThreadList benutzen um Variablen im Hauptthread zu schü
 
Zitat:

Zitat von spaxxn
Da würde das Verwenden der bereits vorhandenen Listen in Verbindung mit CriticalSection's schneller gehen.

Du meinst schneller vom Programmablauf her, oder vom implementieren in den Code?

EConvertError 21. Mai 2008 09:57

Re: TThreadList benutzen um Variablen im Hauptthread zu schü
 
Hallo!

Willkommen in der Delphi-Praxis!

Ich würde es mir nicht sooo einfach machen: Gerade im VCL-Thread kann eine CriticalSection ein Einfrieren der Form bedeuten.

Ich kann dir das Tutorial Multithreading The Delphi Way empfehlen. Da lernt man IMHO alles, was man über Threads und Synchronisation wissen sollte.

In Frage kommen je nach genauer Anforderung auch Mutexes, Semaphoren oder der dort vorgestellte "blocking to asynchronous buffer". In diesem Beispiel wird übrigens auch - so ähnlich wie du es willst - mit einer Komponente gearbeitet (TComponent ist aber keine visuelle Komponente). Da müsste man genaueres über deine Anforderungen wissen. Aber wie gesagt: Das Tutorial sollte alle Fragen beantworten.

Man sollte das IMHO irgendwie auch mal in die CodeLib geben oder anderweitig in der DP leicht auffindbar machen...

Mit freundlichen Grüßen,
Andreas

spaxxn 21. Mai 2008 10:03

Re: TThreadList benutzen um Variablen im Hauptthread zu schü
 
Die Implementation würde schneller gehen und vom Ablauf her wäre der Unterschied zu TThreadList nur marginal...

Beides ist definitv nicht die beste Lösung...


Delphi-Quellcode:
procedure TWorkThread.FetchInput_SHARED;
begin
  EnterCriticalSection(MyCritSect);
  if FOwner.FInputLines_SHARED.Count > 0 then
  begin
    FInputLines_SHARED.Assign(FOwner.FInputLines_SHARED);
    FOwner.FInputLines_SHARED.Clear;
  end;
  LeaveCriticalSection(MyCritSect);
end;
Wie man sieht, passiert nicht viel: Enthält der Puffer der VC Strings, dann werden sie übernommen und danach gelöscht.


Delphi-Quellcode:
procedure TWorkThread.SendOutput_SHARED;
begin
  EnterCriticalSection(MyCritSect);
  if Assigned(FOwner.FOutputLines_SHARED) then
      begin
        FOwner.FOutputLines_SHARED.BeginUpdate;
        if FOwner.FOutputLines_SHARED.Count = 0 then
          begin
            if (FOutputType = otEntireLine) then
              FOwner.FOutputLines_SHARED.Add(FOutputStr)
            else
              FOwner.FOutputLines_SHARED.Text := FOutputStr;
          end
        else
          begin
            // change the way to add by last addstring type
            if FLineBeginned then
              FOwner.FOutputLines_SHARED[FOwner.FOutputLines_SHARED.Count - 1] := FOutputStr
            else
              FOwner.FOutputLines_SHARED.Add(FOutputStr);
          end;
        FOwner.FOutputLines_SHARED.EndUpdate;
      end;
  FLineBeginned := (FOutputType = otBeginningOfLine);
  LeaveCriticalSection(MyCritSect);
end;
Zusätzlich musst du noch MyCritSect vom Typ TRTLCriticalSection definieren und
implementation

var
MyCritSect : TRTLCriticalSection;
...
initialization
InitializeCriticalSection(MyCritSect);
finalization
DeleteCriticalSection(MyCritSect);

einfügen.

Das sollte für dich nur ein Test sein. Für eine richtige implementation ist das ein wenig unschön. Damit hast du dann quasi den Ersatz für die TThreadList geschaffen.


constructor Create( AOwner: TDosCommand ); <- warum TDosCommand, wenn man fragen darf?

Frankieboy82 21. Mai 2008 10:13

Re: TThreadList benutzen um Variablen im Hauptthread zu schü
 
Danke,

werds mir mal durchlesen!
Hier gehts nämlich wirklich um Geschwindigkeit, und ein eingefrorenes Form hattte ich schon sehr oft :(

Der Hauptcode im WorkThread ist eine repeat until Schleife, in deren Mitte SendOutput_SAHRED und deren Ende FetchInput_SHARED, aufgerufen werden.
Die Synchronisation ist da nicht so einfach...
Das letzte Mal haben sich der Main-Thread und der Zusatzthread beide komplett blockiert, als ich im Zusatzthread FOwner.FSync.BeginWrite aufgerufen habe. Das Ergebnis war ein Stillstand der gesamten (Delphi-)Applikation.

Frankieboy82 21. Mai 2008 10:15

Re: TThreadList benutzen um Variablen im Hauptthread zu schü
 
Zitat:

Zitat von spaxxn
constructor Create( AOwner: TDosCommand ); <- warum TDosCommand, wenn man fragen darf?

hm, weil Die Komponente eigentlich TDosCommand heißt... ;-)


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