![]() |
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:
Kurz zur Erlätuerung des des Ganzen:
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; 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 |
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. |
Re: TThreadList benutzen um Variablen im Hauptthread zu schü
Zitat:
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? |
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 |
Re: TThreadList benutzen um Variablen im Hauptthread zu schü
OK, hier mal die Implementation der beiden wichtigen Prozeduren aus TWorkThread:
Delphi-Quellcode:
Wie man sieht, passiert nicht viel: Enthält der Puffer der VC Strings, dann werden sie übernommen und danach gelöscht.
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;
Delphi-Quellcode:
Das hier ist etwas komplizierter, ich habe es aus einem anderen Quelltext übernommen, und muss es noch vereinfachen.
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; |
Re: TThreadList benutzen um Variablen im Hauptthread zu schü
Zitat:
|
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 ![]() 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 |
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:
Wie man sieht, passiert nicht viel: Enthält der Puffer der VC Strings, dann werden sie übernommen und danach gelöscht.
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;
Delphi-Quellcode:
Zusätzlich musst du noch MyCritSect vom Typ TRTLCriticalSection definieren und
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; 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? |
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. |
Re: TThreadList benutzen um Variablen im Hauptthread zu schü
Zitat:
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 16:21 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