![]() |
Probleme mit Enter/LeaveCriticalSection
Hallo zusammen,
ich verstehe das mit der Enter/LeaveCriticalSection irgendwie nicht: bei mir schein dienicht zu locken: Ich habe mir ein kleines eigenes logging zusammengebastelt(ja ich weiss: sowas gibt es schon fertig....dazu bitte keine Ratschläge, weil ich verstehen möchte was ich falsch mache): Der init code(wird einmalig aufgerufen)
Delphi-Quellcode:
und das deinitialisieren
procedure InitLog(path : string);
begin InitializeCriticalSection(FLock); end;
Delphi-Quellcode:
Das Logging, welches aus mehreren Threads(u.a. einige Timer) aufgerufen werden kann:
procedure FinalizeLog;
begin DeleteCriticalSection(FLock); end;
Delphi-Quellcode:
Wie man sieht habe ich outputdebugstring drin, und damit sollte man abwechselnd in,out,in,out,in,out,...
procedure WriteLnLog(output : string;level:Integer=1);
var f_size : Integer; begin if True then begin EnterCriticalSection(FLock); OutputDebugString('in'); try if True then begin if (not fileexists(logpath)) then rewrite(logfile) else append(logfile); if output = '' then writeln(logfile,.........) else writeln(logfile,........); closefile(logfile); end; finally OutputDebugString('out'); LeaveCriticalSection(FLock); end; end; end; erwarten. Leider bekomme ich manchmal: Debug-Ausgabe: in Prozess xxx.exe (3332) Debug-Ausgabe: in Prozess xxx.exe (3332) Debug-Ausgabe: out Prozess xxx.exe (3332) Erste Gelegenheit für Exception bei $7C812A7B. Exception-Klasse EInOutError mit Meldung 'E/A-Fehler 103'. Prozess xxx (3332) Warum die exception kommt ist klar: da ist die datei einfach zu, aber was mich wundert ist warum ich überhaupt 2* in diese sektion komme. Kann mir mal irgendjemand erklären was ich da falsch mache? (nebenbei:ist BDS2006) Danke schon malfür eure Mühe! |
AW: Probleme mit Enter/LeaveCriticalSection
CriticalSection funktionieren nur mit Threads nicht mit Timer. Häng mal die Timer ab, dann müsste es passen.
Delphi-Quellcode:
was das?
it True then
|
AW: Probleme mit Enter/LeaveCriticalSection
Timer? Doch, geht. Eine CriticalSection blockt zuverlässig alles ab, was da rein will - unabhängig davon, wo es herkommt.
In meinen Augen ein lohnenswerterer Ansatz: Nimm doch ein Objekt vom Typ TCriticalSection. Dort ist alles artig gekapselt und Du musst nur .Enter / .TryEnter / .Leave aufrufen. Funktioniert in meinen Augen zuverlässig wie ein Panzer. |
AW: Probleme mit Enter/LeaveCriticalSection
Bei Logs finde ich eine asynchrone Lösung sinnvoller. Das heißt ich nehme einen Thread, der nur die Logs schreibt und synchronisiere in irgendeiner Form mit diesem Thread um die Daten asynchron dort hineinzupacken.
Zum Beispiel mit TThread.Queue(LogThread, ...) oder indem Windows Messages geschickt werden oder... Das hat den Vorteil, dass die sendenden Threads nicht blockiert werden. Ob TCriticalSection oder direkt die API-Funktionen macht vom Ergebnis her keinen Unterschied, da TCriticalSection diese nur kapselt. Allerdings ist es natürlich sinnvoll TCriticalSection zu verwenden um für andere Betriebssysteme gerüstet zu sein. Der Quelltext wie er jetzt ist sieht aber eigentlich korrekt aus. |
AW: Probleme mit Enter/LeaveCriticalSection
Zitat:
Der gezeigte Code fällt allerdings offenbar nicht darunter, aber andererseits ist er auch nicht mal compilierbar. |
AW: Probleme mit Enter/LeaveCriticalSection
Hallo,
dass der code nicht compilierbar ist ist richtig: ich habe ihn auch auf die wesentlichen Punkte runter gestrippt um zu zeigen wo mein Problem ist. Aber wie bekomme ich das Ding denn nun wirklich so zu, dass der Code nicht parallel durchlaufen werden kann? |
AW: Probleme mit Enter/LeaveCriticalSection
Zitat:
Versuch doch mal den Code so zu kürzen, dass er im Wesentlichen noch funktioniert und schaue ob das Problem noch auftritt. Wenn ja, kannst du dann den Code 1:1 zeigen, der das Problem verursacht. Bzw. wenn du möchtest, kannst du den auch so wie er ist posten. |
AW: Probleme mit Enter/LeaveCriticalSection
Mit der CriticalSection stellst Du ja schon mal sicher, dass sich verschiedene Threads nicht gegenseitig in die Quere kommen. Zur Not nutzt Du mal ein boolsches Flag, um zu erkennen, wer da wie oft rein will. Das ist keine beständige Lösung, könnte Dir aber einen schnellen Erkenntnisgewinn bringen, wenn Du einen Breakpoint auf die Stelle setzt, an der er ein zweites Mal rein will und Dir dann den Callstack ansiehst.
|
AW: Probleme mit Enter/LeaveCriticalSection
@Daniel,
die Idee an sich ist nicht schlecht... hatte ich auch schon. Hat aber nur 2 Nachteile: - wenn du weisst woher er beim 2. Mal kam ist schön, aber damit weisst du noch nicht woher er beim 1. Mal kam :? - Das Mit dem Boolschen Flag funktioniert dann auch nur durch Zufall, weil der Zugriff darauf auch nicht atomar ist @Sebastian Tendenziell glaube ich nicht daran, dass ich den Fehler mit dem Strippen beseitigt habe: die Beiden Trues kommen aus der Abfrage ob logging aktiv ist und welcher loglevel aktiv ist, und im writeln wird der String vorher noch verschlüsselt |
AW: Probleme mit Enter/LeaveCriticalSection
Wie schon erwähnt, ist es einem einzelnen Thread durchaus erlaubt, eine CriticalSection mehrfach zu betreten, was zu dem von dir geschilderten Effekt führen kann. Ein mögliches Szenario hatte ich bereits beschrieben. Ohne den vollständigen Originalcode, bei dem der Fehler auftritt, kann man aber nichts mehr dazu sagen.
BTW, warum ist logfile keine lokale Variable? |
AW: Probleme mit Enter/LeaveCriticalSection
Zitat:
Es kommt 2x "enter", das Programm läuft natürlich in einer Endlosschleife. Wo liegt mein Denkfehler?
Delphi-Quellcode:
var
CritcalSection: TCriticalSection; implementation {$R *.dfm} procedure TForm6.Button1Click(Sender: TObject); begin Timer1.Enabled := True; CS; end; procedure TForm6.CS; begin CritcalSection.Enter; try OutputDebugString('enter'); while True do begin Application.ProcessMessages; sleep(100); end; finally CritcalSection.Leave; OutputDebugString('leave'); end; end; procedure TForm6.Timer1Timer(Sender: TObject); begin Timer1.Enabled := False; CS; end; initialization CritcalSection := TCriticalSection.Create; finalization CritcalSection.Free; end. |
AW: Probleme mit Enter/LeaveCriticalSection
Mach es mit Threads und ohne Timer.
|
AW: Probleme mit Enter/LeaveCriticalSection
Ich verstehe die Motivation nicht- das ist doch arg konstruiert. Was bringt das
Delphi-Quellcode:
außer dass du
while True do Application.ProcessMessages()
|
AW: Probleme mit Enter/LeaveCriticalSection
Application.ProcessMessages arbeitet alle anstehenden Windows-Messages ab. Somit natürlich auch die Button-Clicks und die Timer-Events. Das fürhrt dazu, daß der Timer-Event nochmal aufgerufen wird, obwohl der erste wegen der Endlosschleife noch gar nicht beendet wurde.
Ich vermute mal, du hast mindestens zweimal auf den Button geklickt? |
AW: Probleme mit Enter/LeaveCriticalSection
Zitat:
Wenn alles in TTimern ist, dann läuft alles im Hauptthread und da eine CriticalSection mehrfache Zugriffe vom selben Thread erlaubt, wird auch alles durchgelassen. Das ist, damit ein Thread sich nicht selber sperrt, wenn er mehrmals durch die selbe CS will/muss. * Also entweder keine Timer/Messages verwenden und Zugriffe aus unterschiedlichen Threads * oder eine andere Sperre verwenden, welche nicht threadaffine arbeitet. System.SyncObjs.pas (TMonitor, TCriticalSection, TMultiReadSingleWriter und Co. arbeiten threadaffine) PS: Man kann auch den Button deaktivieren, so lange das OnClick arbeitet. (ist nur bissl unschön, wenn der Button fukusierbar ist und der Bokus danach wo anders steht) |
AW: Probleme mit Enter/LeaveCriticalSection
Wenn Du CS aufgerufen hast, kommst Du da nie wieder raus...
Da die CS den Main-Thread blockt du aber mit dem Button Klick im Main Thread bist, kannst Du X-Mal den Button drucken und er wird immer wieder reinlaufen und noch eine CS auf den Stack packen. |
AW: Probleme mit Enter/LeaveCriticalSection
Zitat:
Der Threadersteller hat ein Mix aus Threads und Timer(n) und versucht es mit einer CriticalSection zu syncronisieren und das ist keine gut Idee. |
AW: Probleme mit Enter/LeaveCriticalSection
Hallo zusammen,
aber ich dachte immer ein onTimer macht einen eigenen Thread auf, oder habe ich da jetzt was falsch verstanden? Und welche locks arbeiten NICHT "threadaffin"? |
AW: Probleme mit Enter/LeaveCriticalSection
Timer laufen im Hauptthread und funktionieren über Windows-Messages (WM_TIMER) genau wie Button-Klicks o.ä.
D.h. zwei Timer können auch dementsprechend NIE gleichzeitig laufen, weswegen du keine Critical Section brauchst wenn du nur Timer benutzt. Bei Threads brauchst du Critical Sections. Und beides zu mischen ist wie schon gesagt keine gute Idee. |
AW: Probleme mit Enter/LeaveCriticalSection
Zitat:
|
AW: Probleme mit Enter/LeaveCriticalSection
Zitat:
TimerOnTimer Aufrufe sind immer im Mainthread |
AW: Probleme mit Enter/LeaveCriticalSection
Zitat:
|
AW: Probleme mit Enter/LeaveCriticalSection
Ein solcher Lock könnte so aussehen:
Delphi-Quellcode:
Der Mainthread macht ungefähr so etwas:
Flag := False;
while (not Flag) do;
Delphi-Quellcode:
ProgrammCodeAusführen reagiert z.B. mal auf das Klicken eines Buttons oder auf das Feuern eines Timers.
repeat
TastaturAbfragen; MausAbfragen; EreignisseAbfragen; ControlsZeichnen; ProgrammCodeAusführen; until WennProgrammGeschlossen Wenn ProgrammCodeAusführen lange dauert, dann kann der Rest erst mal auch nicht mehr erfüllt werden und das Programm "hängt". Wenn Du im Mainthread o.g. Lock verwendest kommt das Programm nie mehr raus, da Flag nie auf True gesetzt werden wird. Ohne nebenläufige Threads, die das Flag wieder umschalten, kann man so etwas also nicht umsetzen. Ich hoffe, dass das nicht ganz falsch ist (wenngleich natürlich stark vereinfacht). Eine Überlegung zum ProcessMessages: Wenn im ProgrammCodeAusführen Application.ProcessMessages aufgerufen wird, werden unbehandelte Messages abgearbeitet. Dazu wird ProgrammCodeAusführen unterbrochen und ggf. rekursiv erneut aufgerufen. Ist das so richtig? Insofern dürften die Timer im Beispiel die Logs nicht mehr durcheinander bringen, wenn man das ProcessMessages weg lässt, da sich die Timerbehandlungen dann immer schön in den Kreislauf des Mainthreads einfügen und kein rekursiver Aufruf erfolgen kann. Richtig? (Ich kann das jetzt nicht testen) |
AW: Probleme mit Enter/LeaveCriticalSection
Zitat:
Beispiel:
Delphi-Quellcode:
Einfach mal Application.Processmessage rein nehmen und "wundern"...
procedure TForm189.Button1Click(Sender: TObject);
begin Timer1.Enabled := true; end; procedure TForm189.FormCreate(Sender: TObject); begin FCount := 0; Timer1.Interval := 25; end; procedure TForm189.Timer1Timer(Sender: TObject); begin Memo1.Lines.Add(FCount.ToString); inc(FCount); Sleep(100); // Application.ProcessMessages; dec(FCount); end; |
AW: Probleme mit Enter/LeaveCriticalSection
Liste der Anhänge anzeigen (Anzahl: 2)
Ich habe das nochmal ein wenig umgebaut.
Hier wird ProcessMessages 20 mal ausgeführt und dann 20 mal nicht mehr. Dann wird der Timer ausgeschaltet und dadurch die ersten 20 Logs "nachgeholt". (Anbei Projekt für XE3 + Exe) EDIT: Noch eine kleine Änderung:
Delphi-Quellcode:
So sieht man, dass der Rest noch abgearbeitet wird, während der Timer schon deaktiviert ist.
procedure TForm1.Timer1Timer(Sender: TObject);
var I: Integer; S: string; begin if (Memo1.Lines.Count > 40) then Timer1.Enabled := False; inc(FCount); Memo1.Lines.Add(IntToStr(FCount) + ' ' + FormatDateTime('zzz', Now) + ': '); for I := 0 to 20 do begin Sleep(100); if Timer1.Enabled then S := '+' else S := '-'; Memo1.Lines[Memo1.Lines.Count - 1] := Memo1.Lines[Memo1.Lines.Count - 1] + ' ' + IntToStr(Memo1.Lines.Count) + S + IntToStr(I); if (Memo1.Lines.Count < 20) then Application.ProcessMessages; end; dec(FCount); end; |
AW: Probleme mit Enter/LeaveCriticalSection
Zitat:
|
AW: Probleme mit Enter/LeaveCriticalSection
In Deinem Test kommt das Programm nie dazu, die zurückgestellten Aufgaben abzuarbeiten. Es hängt sich letztlich auf und lässt sich nicht beenden.
Mein Test zeigt (besser), dass die zurückgestellten Logs später noch abgearbeitet werden (nämlich wenn keine weiteren ProcessMessages mehr dazwischen funken). Ist ja nicht weiter wichtig, aber führt vielleicht schneller zur Erleuchtung bei denjenigen, die sich mal damit auseinander setzen wollen. |
AW: Probleme mit Enter/LeaveCriticalSection
Zitat:
Ich logge per Thread in einen Queue, damit mein Hauptprogramm durch Threading angehalten wird... Aus dieser Queue schreibe ich dann aus einem Thread per Queue nicht syncronize auf die UI. |
AW: Probleme mit Enter/LeaveCriticalSection
Zitat:
Gruß K-H |
AW: Probleme mit Enter/LeaveCriticalSection
Huch!?
Ich habe Dein Beispiel aus #24 nachgebaut und mich anweisungsgemäß gewundert. :mrgreen: Dort hast Du einen Timer benutzt, von Threads ist dort nichts erkennbar. Ich habe die Demo etwas erweitert, damit man noch besser sieht, dass Application.ProcessMessages die Timer-Behandlung unterbricht und rekursiv aufruft. Du hast doch damit angefangen!!! :stupid: (ich habe das nur etwas erweitert) Das Eingangsproblem war doch, dass die erwartete serielle Abarbeitung trotz CS nicht funktionierte. Es ist m.E. ersichtlich geworden, dass dies durch Application.ProcessMessages verursacht ist. EDIT: Man könnte natürlich das Beispiel noch um Threads und eine CriticalSection erweitern um die Ausgangssituation noch genauer nachzubilden, aber ich denke, das ist nicht unbedingt nötig. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 21:31 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