[gelöst]Thread in einer cli application
[gelöst]Thread in einer cli application
Hallo Lazarus Gemeinde
Ich wollte vor langer Zeit mal ein solches Projekt anfangen bin dann aber daran gescheitert das Synchronize(); nicht wirklich funktioniert.
Nach viel Suchen und lesen fand ich dann heraus das man im Main.Thread CheckSynchronize() immer wieder mal aufrufen muss damit das Synchronize() vom Thread verarbeitet werden kann.
Das habe ich dann so versucht und es hat nicht geklappt. In einer Schleife wird CheckSynchronize() aufgerufen, aber beim 2ten oder 3ten mal bekomme ich dann ein "sigsegv-Fehler" und leider keine Infos die ich verstehen kann.
Allerdings ist in der cli App vorgesehen das der User jeder Zeit eine Eingabe machen kann. Daher verwende ich ReadLn; was ja dann jegliche Schleifen NICHT ermöglicht. Ich kann dann nicht immer mal CheckSynchronize() aufrufen.
Dann habe ich WakeMainThread gefunden und damit scheint es zu funktionieren. Ich weise dem WakeMainThread eine Procedure zu, die sich im MainThread/class befindet. Im Thread wird dann WakeMainThread an den entsprechenden Stellen aufgerufen und ich bekomme einen output auf der Console.
Meine Frage ist, ob das mit dem WakeMainThread die richtige/beste Lösung ist?
Ich wollte vor langer Zeit mal ein solches Projekt anfangen bin dann aber daran gescheitert das Synchronize(); nicht wirklich funktioniert.
Nach viel Suchen und lesen fand ich dann heraus das man im Main.Thread CheckSynchronize() immer wieder mal aufrufen muss damit das Synchronize() vom Thread verarbeitet werden kann.
Das habe ich dann so versucht und es hat nicht geklappt. In einer Schleife wird CheckSynchronize() aufgerufen, aber beim 2ten oder 3ten mal bekomme ich dann ein "sigsegv-Fehler" und leider keine Infos die ich verstehen kann.
Allerdings ist in der cli App vorgesehen das der User jeder Zeit eine Eingabe machen kann. Daher verwende ich ReadLn; was ja dann jegliche Schleifen NICHT ermöglicht. Ich kann dann nicht immer mal CheckSynchronize() aufrufen.
Dann habe ich WakeMainThread gefunden und damit scheint es zu funktionieren. Ich weise dem WakeMainThread eine Procedure zu, die sich im MainThread/class befindet. Im Thread wird dann WakeMainThread an den entsprechenden Stellen aufgerufen und ich bekomme einen output auf der Console.
Meine Frage ist, ob das mit dem WakeMainThread die richtige/beste Lösung ist?
Zuletzt geändert von hubblec4 am Mo 10. Jun 2019, 02:58, insgesamt 1-mal geändert.
-
- Beiträge: 2118
- Registriert: Di 23. Sep 2014, 17:46
- OS, Lazarus, FPC: Win10 | Linux
- CPU-Target: x86_64
Re: Thread in einer cli application
1. Warum machst du blockierende operationen (ReadLn auf einem blockenden filedeskriptor) im main thread?
Entweder verschieb das lesen in nen separaten thread der so lange blocken kann wie du willst, oder lies ohne zu blocken:
Hier mal ne nonblocking readln funktion von einem meiner projekte:
Für die Uses benötigst du:
NonBlockingStdIn kannst du definieren, falls du sicher bist das das programm am ende auf einem Unix laufen wird auf dem man STDIN nonblocking machen kann. Soweit ich weiß sollte das auf allen modernen Linux versionen möglich sein, wenn du dir nicht sicher bist ob der zielrechner das unterstützt, definiere es einfach nicht
2. Warum verwendest du synchronize? Für GUI anwendungen wird es benötigt weil du nur vom main aus auf GUI elemente zugreifen kannst. Bei eine CLI anwendung gibts keine GUI, du musst also nicht alles über den main thread leiten. Locks/CriticalSections sollten zur verhinderung von race conditions völlig ausreichen
Entweder verschieb das lesen in nen separaten thread der so lange blocken kann wie du willst, oder lies ohne zu blocken:
Hier mal ne nonblocking readln funktion von einem meiner projekte:
Code: Alles auswählen
{$IfDef UNIX}
function ReadChar(Blocking: boolean = True): char;
var
oTIO, nTIO: Termios;
{$IfDef NonBlockingStdIn}
flags,
{$Else}
fdsin: tfdSet;
{$EndIf}
res: integer;
begin
res := 1;
Result := #0;
TCGetAttr(1, oTIO);
nTIO := oTIO;
CFMakeRaw(nTIO);
TCSetAttr(1, TCSANOW, nTIO);
if not Blocking then
begin
{$ifDef NonBlockingStdIn}
flags := FpFcntl(StdInputHandle, F_GetFl, 0);
FpFcntl(StdInputHandle, F_SetFl, flags or O_NONBLOCK);
{$Else}
fpFD_ZERO(fdsin);
fpFD_SET(StdInputHandle, fdsin);
res := fpSelect(StdInputHandle + 1, @fdsin, nil, nil, 0);
{$EndIf}
end;
if res > 0 then
res := FpRead(StdInputHandle, Result, 1);
{$ifDef NonBlockingStdIn}
if res = 0 then
Result := #0;
{$EndIf}
//restore settings
TCSetAttr(1, TCSANOW, oTIO);
{$ifDef NonBlockingStdIn}
if not Blocking then
FpFcntl(StdInputHandle, F_SetFl, flags);
{$EndIf}
end;
{$Else}
var
NextChars: string;
// found at http://www.cplusplus.com/forum/articles/19975/
function ReadChar(Blocking: boolean = True): char;
var
STDInHandle: HANDLE;
ConsoleInput: INPUT_RECORD;
RecordCount: cardinal;
begin
if NextChars.Length > 0 then
begin
Result := NextChars.Chars[0];
NextChars := NextChars.Substring(1);
Exit;
end;
STDInHandle := GetStdHandle(STD_INPUT_HANDLE);
GetNumberOfConsoleInputEvents(STDInHandle, RecordCount);
if Blocking or (RecordCount > 0) then
while ReadConsoleInputA(STDInHandle, ConsoleInput, 1, RecordCount) do
if (ConsoleInput.EventType = KEY_EVENT) and
(ConsoleInput.Event.KeyEvent.wVirtualKeyCode <> VK_SHIFT) and
(ConsoleInput.Event.KeyEvent.wVirtualKeyCode <> VK_MENU) and
(ConsoleInput.Event.KeyEvent.wVirtualKeyCode <> VK_CONTROL) and
(ConsoleInput.Event.KeyEvent.bKeyDown) then
begin
if (ConsoleInput.Event.KeyEvent.wVirtualKeyCode >= VK_PRIOR) and
(ConsoleInput.Event.KeyEvent.wVirtualKeyCode <= VK_HOME) then
begin
case ConsoleInput.Event.KeyEvent.wVirtualKeyCode of
VK_HOME: NextChars := '[H';
VK_END: NextChars := '[F';
VK_PRIOR: NextChars := '[5~';
VK_NEXT: NextChars := '[6~';
end;
Result := #27;
end
else if (ConsoleInput.Event.KeyEvent.wVirtualKeyCode >= VK_F1) and
(ConsoleInput.Event.KeyEvent.wVirtualKeyCode <= VK_F12) then
begin
case ConsoleInput.Event.KeyEvent.wVirtualKeyCode of // F-Keys
VK_F1: NextChars := #79'P';
VK_F2: NextChars := #79'Q';
VK_F3: NextChars := #79'R';
VK_F4: NextChars := #79'S';
VK_F5: NextChars := #91#49#53'~';
VK_F6: NextChars := #91#49#55'~';
VK_F7: NextChars := #91#49#56'~';
VK_F8: NextChars := #91#49#57'~';
VK_F9: NextChars := #91#50#48'~';
VK_F10: NextChars := #91#50#49'~';
VK_F11: NextChars := #91#50#50'~';
VK_F12: NextChars := #91#50#52'~';
end;
Result := #27;
end
else if ConsoleInput.Event.KeyEvent.dwControlKeyState and ($8 or $4) <> 0 then
begin
if (ConsoleInput.Event.KeyEvent.wVirtualKeyCode >= Ord('A')) and
(ConsoleInput.Event.KeyEvent.wVirtualKeyCode <= Ord('Z')) then
Result := chr(ConsoleInput.Event.KeyEvent.wVirtualKeyCode - Ord('A') + 1)
else if (ConsoleInput.Event.KeyEvent.wVirtualKeyCode >= Ord('3')) and
(ConsoleInput.Event.KeyEvent.wVirtualKeyCode <= Ord('7')) then
Result := chr(ConsoleInput.Event.KeyEvent.wVirtualKeyCode - Ord('3') + 27);
end
else
case ConsoleInput.Event.KeyEvent.AsciiChar of
#8, #27, #13, #32..#254:
if ConsoleInput.Event.KeyEvent.dwControlKeyState and $2 = $2 then
begin
NextChars := ConsoleInput.Event.KeyEvent.AsciiChar;
Result := #27;
end
else
Result := ConsoleInput.Event.KeyEvent.AsciiChar;
#9:
begin
if ConsoleInput.Event.KeyEvent.dwControlKeyState and $0010 = $0010 then
begin
NextChars := '[Z';
Result := #27;
end
else
Result := #9;
end;
#0:
begin
// Arrows
SetLength(NextChars, 2);
NextChars[1] := '[';
case ConsoleInput.Event.KeyEvent.wVirtualKeyCode of
$25: NextChars[2] := 'D';
$26: NextChars[2] := 'A';
$27: NextChars[2] := 'C';
$28: NextChars[2] := 'B';
end;
Result := #27;
end;
end;
Exit;
end
else
if not Blocking then
break;
Result := #0;
end;
{$EndIf}
function ReadSequence(Blocking: boolean = True): string;
var
c: char;
l: integer;
begin
SetLength(Result, 1);
Result[1] := ReadChar(Blocking);
if Result[1] = #0 then
begin
SetLength(Result, 0);
Exit;
end
else if Result[1] <> #27 then
exit;
l := 1;
repeat
c := ReadChar(False);
if c > #0 then
begin
Inc(l);
if l > Result.Length then
SetLength(Result, Result.Length * 2);
Result[l] := c;
end;
until c = #0;
SetLength(Result, l);
end;
Code: Alles auswählen
uses ...
{$IfDef UNIX}
, BaseUnix, termio
{$Else}
, Windows
{$EndIf} ;
2. Warum verwendest du synchronize? Für GUI anwendungen wird es benötigt weil du nur vom main aus auf GUI elemente zugreifen kannst. Bei eine CLI anwendung gibts keine GUI, du musst also nicht alles über den main thread leiten. Locks/CriticalSections sollten zur verhinderung von race conditions völlig ausreichen
-
- Beiträge: 3444
- Registriert: Mo 11. Sep 2006, 10:24
- OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
- CPU-Target: X32 / X64 / ARMv5
- Wohnort: Krefeld
Re: Thread in einer cli application
CheckSynchronize() ist OK.
Es muss im Mainthread aufgerufen werden.
Der Mainthread darf auf nichts warten.
Ich habe vor längerer Zeit mal eine "WidgetType" Implementierung gemacht, die auch funktioniert hat.
Durch CheckSynchronize() werden da im Mainthread alleTTimer Events und Events bearbeitet, die von Worker-Threads provoziert werden. (TThread.Synchronize, TThread.Queue, PostMessage, ...) .
Worker-Threads dürfen in Blocking read etc. warten.
-Michael
Es muss im Mainthread aufgerufen werden.
Der Mainthread darf auf nichts warten.
Ich habe vor längerer Zeit mal eine "WidgetType" Implementierung gemacht, die auch funktioniert hat.
Durch CheckSynchronize() werden da im Mainthread alleTTimer Events und Events bearbeitet, die von Worker-Threads provoziert werden. (TThread.Synchronize, TThread.Queue, PostMessage, ...) .
Worker-Threads dürfen in Blocking read etc. warten.
-Michael
Re: Thread in einer cli application
Wie schon geschrieben, habe ich es getestet, aber nach dreimaligem Aufruf von CheckSynchronize() bekomme ich einen Laufzeit Fehler und alles stürtzt ab, keine Fehlerausgabe, auch dann nicht wenn ich einen Try-Except-Block nutze.mschnell hat geschrieben:CheckSynchronize() ist OK.
Es muss im Mainthread aufgerufen werden.
Der Mainthread darf auf nichts warten.
Für eine GUI ist Synchrozie() völlig OK, es macht was es soll. Worker-Thread unterbrechen und eine Methode im Main.Thread ausführen.
Aber bei der cli ist klar, da gibts keine GUI.
@Warf
Danke für den Code, da les ich erstmal nur Bahnhof

Im normal Fall erledigt ja eine cli app eine gewisse aufgabe und gibt halt bissl Infos zurück. Da aber die Aufgabe sehr lange dauern kann soll es nur eine "UserExit" Option geben. Dazu muss man nur ein "e" eingeben und die Enter-Taste drücken.
Ich weis jetzt nicht wie sinnvoll das ist, dieses Auslesen in einen eigenen Thread zu packen.
Re: Thread in einer cli application
@Warf
Habe eben mal deinen Code in eine neue Unit gepackt(ConsoleRead) und es funktioniert super.
Eingabe des Users:
Kann es sein das ich bei der Eingabe für einen Befehl nicht mehr die Enter-Taste drücken muss damit die "Line" gelesen wird.
In meiner App soll einfach ein "e" für Exit genutzt werden. Ich tippe "e" und Exit wird auf der Console ausgegeben.
Was aber wenn es noch ein zweites Kommando gäbe das zum Beispiel "einmal" heist.
Wenn ich nun das "e" (erstes Zeichen) eingebe wird dann nicht sofort mein definierter "Exit"-Befehl ausgeführt?
Habe eben mal deinen Code in eine neue Unit gepackt(ConsoleRead) und es funktioniert super.
Eingabe des Users:
Kann es sein das ich bei der Eingabe für einen Befehl nicht mehr die Enter-Taste drücken muss damit die "Line" gelesen wird.
In meiner App soll einfach ein "e" für Exit genutzt werden. Ich tippe "e" und Exit wird auf der Console ausgegeben.
Was aber wenn es noch ein zweites Kommando gäbe das zum Beispiel "einmal" heist.
Wenn ich nun das "e" (erstes Zeichen) eingebe wird dann nicht sofort mein definierter "Exit"-Befehl ausgeführt?
Re: Thread in einer cli application
Ich habe nun auch alles auf Synchronize() mit Hilfe von CheckSynchronize() umgestellt und komischerweise geht es jetzt aufeinmal.
"Einmal" wird niemals als User_input ausgegeben. Immer nur ein Zeichen wird gelesen.
Wenn ich bei CheckSynchronize() als Wert nur eine 10 eingebe dann dauert das "Kopieren"(als Aufgabe des Threads) seeeehr lange und es scheint auch als ob nicht immer was synchronisiert wird (gibt wahrscheinlich noch kein Snychronize aus dem Worker-Thread).
Diese Wert (500) bedeutet ja nur das solange gewartet werden soll, aber gibt es bereits ein Synchronize das verabeitet werden muss wird nicht die volle Zeit gwartet, Oder liege ich da falsch?
Code: Alles auswählen
user_input : String;
FUserExit : Boolean;
while not FFinished do
begin
user_input:=ReadSequence(false); // input lesen neu
if user_input <> '' then
WriteLn(#13#10'User-Input: '+user_input);
//ReadLn(user_input); // input lesen alt
if user_input = 'e' then
begin
FUserExit:=true;
Sleep(100);
Break;
end;
if user_input = 'einmal' then
begin
Tu_Einmal_Was;
end;
//Sleep(100);
CheckSynchronize(500);
end;
Wenn ich bei CheckSynchronize() als Wert nur eine 10 eingebe dann dauert das "Kopieren"(als Aufgabe des Threads) seeeehr lange und es scheint auch als ob nicht immer was synchronisiert wird (gibt wahrscheinlich noch kein Snychronize aus dem Worker-Thread).
Diese Wert (500) bedeutet ja nur das solange gewartet werden soll, aber gibt es bereits ein Synchronize das verabeitet werden muss wird nicht die volle Zeit gwartet, Oder liege ich da falsch?
Re: Thread in einer cli application
Kann der Worker-Thread einfach WritLn ausführen und auf die Console schreiben? Das stört den Main.Thread dann nicht?Warf hat geschrieben: 2. Warum verwendest du synchronize? Für GUI anwendungen wird es benötigt weil du nur vom main aus auf GUI elemente zugreifen kannst. Bei eine CLI anwendung gibts keine GUI, du musst also nicht alles über den main thread leiten. Locks/CriticalSections sollten zur verhinderung von race conditions völlig ausreichen
-
- Lazarusforum e. V.
- Beiträge: 3178
- Registriert: Di 22. Jul 2008, 19:27
- OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
- CPU-Target: 32bit x86 armhf
- Wohnort: Köln
- Kontaktdaten:
Re: Thread in einer cli application
Richtig, wenn keine Anforderung da ist, wird maximal 500 Millisekunden gewartet. Falls Anforderungen vorhanden sind oder innerhalb der Wartezeit ankommen, werden diese vollständig der Reihe nach abgearbeitet - dabei ist es egal, wie lange die Bearbeitung dauert.hubblec4 hat geschrieben:Diese Wert (500) bedeutet ja nur das solange gewartet werden soll, aber gibt es bereits ein Synchronize das verabeitet werden muss wird nicht die volle Zeit gwartet, Oder liege ich da falsch?
Ein Programm sollte auf jede Datei mit maximal einem Thread zugreifen. Falls also der Worker-Thread schreibt, sollte der der Main-Thread nicht schreiben. Ansonsten kann es zu zerstückelten Ausgaben kommen, da die Threads abwechselnd unkoordiniert ihren Text ausgeben.hubblec4 hat geschrieben:Kann der Worker-Thread einfach WritLn ausführen und auf die Console schreiben? Das stört den Main.Thread dann nicht?
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein
- fliegermichl
- Lazarusforum e. V.
- Beiträge: 1639
- Registriert: Do 9. Jun 2011, 09:42
- OS, Lazarus, FPC: Lazarus Fixes FPC Stable
- CPU-Target: 32/64Bit
- Wohnort: Echzell
Re: Thread in einer cli application
Der worker thread kann im Prinzip alles machen. Das einzige, was per Synchronize gemacht werden muss ist, wenn ein Thread Variablen im Mainthread schreiben will.
-
- Beiträge: 3444
- Registriert: Mo 11. Sep 2006, 10:24
- OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
- CPU-Target: X32 / X64 / ARMv5
- Wohnort: Krefeld
Re: Thread in einer cli application
Genau das habe ich damals gemacht. (Ein Widget-Type ohne GUI.)hubblec4 hat geschrieben:Aber bei der cli ist klar, da gibts keine GUI.
Es funktioniert.
-Michael
-
- Beiträge: 3444
- Registriert: Mo 11. Sep 2006, 10:24
- OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
- CPU-Target: X32 / X64 / ARMv5
- Wohnort: Krefeld
Re: Thread in einer cli application
Wenn Du keine GUI-Anbindung in Dein Projekt einbindest, sind all Threads gleichberechtigt. Jeder kann alles machen. Du misst dich aber selbst darum kümmern ovb / in wie weit das Thread-fest ist.hubblec4 hat geschrieben:Kann der Worker-Thread einfach WritLn ausführen und auf die Console schreiben? Das stört den Main.Thread dann nicht?
Writeln ist insofern threadfest dass es nicht abstürzt, Und insofern nicht threadfest, dass die Texte, die gleichzeitig von mehreren Threads kommen, buchstabenweise gemischt werden können.
-Michael
-
- Beiträge: 3444
- Registriert: Mo 11. Sep 2006, 10:24
- OS, Lazarus, FPC: svn (Window32, Linux x64, Linux ARM (QNAP) (cross+nativ)
- CPU-Target: X32 / X64 / ARMv5
- Wohnort: Krefeld
Re: Thread in einer cli application
Ein Riesen-Vorteil von "Synchronize" ist dass man (bei Verwendung einer entsprechender Infrastruktur-Libnragry) TTimer verwenden kann und das Projekt genauso "RAD-mäßig" anlegen kann wie ein "normales" Lazarus-Projekt.fliegermichl hat geschrieben:Der worker thread kann im Prinzip alles machen. Das einzige, was per Synchronize gemacht werden muss ist, wenn ein Thread Variablen im Mainthread schreiben will.
-Michael
Re: Thread in einer cli application
OK. Bei mir gibt es nur einen Worker-Thread der auf die Dateien zugreift. Der Main.Thread gibt nur den Progress und andere Meldungen aus. Und kann halt noch User-Eingaben empfangen.Socke hat geschrieben:Ein Programm sollte auf jede Datei mit maximal einem Thread zugreifen. Falls also der Worker-Thread schreibt, sollte der der Main-Thread nicht schreiben. Ansonsten kann es zu zerstückelten Ausgaben kommen, da die Threads abwechselnd unkoordiniert ihren Text ausgeben.hubblec4 hat geschrieben:Kann der Worker-Thread einfach WritLn ausführen und auf die Console schreiben? Das stört den Main.Thread dann nicht?
Ja das dachte ich mir schon, denn es knallte als ich das ohne Synchronize probiert hatte.fliegermichl hat geschrieben:Der worker thread kann im Prinzip alles machen. Das einzige, was per Synchronize gemacht werden muss ist, wenn ein Thread Variablen im Mainthread schreiben will.
Mmh wenn du magst kann ich mir deine Arbeit mal anschauen, wo finde ich diese? Kenne mich mit "Widget" nicht so aus.mschnell hat geschrieben:Genau das habe ich damals gemacht. (Ein Widget-Type ohne GUI.)hubblec4 hat geschrieben:Aber bei der cli ist klar, da gibts keine GUI.
Es funktioniert.
-Michael
Soweit läuft jetzt auch schon alles recht gut. Danke für eure Hilfe.
Das Abfangen des User-Inputs habe ich auch hinbekommen. Da anscheinend immer nur ein Zeichen ausgelesen werden kann (wenn Blocking=false), sammel ich die Zeichen und erst bei ENTER wird das Commando ausgewertet und verarbeitet.
Ich werde noch bissl testen und dann das Thema als GELÖST markieren.
-
- Beiträge: 2118
- Registriert: Di 23. Sep 2014, 17:46
- OS, Lazarus, FPC: Win10 | Linux
- CPU-Target: x86_64
Re: Thread in einer cli application
Eventuell sollte ich einfach mal erklären was die funktion von mir macht.
Angenommen wir sind unter Linux (windows macht das ganze ein bisschen anders und viel komplizierter aus, siehe die funktion von mir). Die eingabe und ausgabe von Programmen wird da als datei gehandhabt, STDIN und STDOUT (es gibt noch ne error ausgabe STDERR, die ignoriere ich aber mal gekonnt), mit dem FileDeskriptoren 0 und 1.
Wenn der nutzer was in die konsole eingibt wird das praktisch in den virtuellen File STDIN (0) geschrieben, und das wartet dann darauf gelesen zu werden. Wurde noch nichts reingeschrieben würde ein lese versuch so lange blockieren (also warten) bis etwas zum auslesen drin ist.
Wenn man jetzt nicht blockend arbeiten möchte, kann man entweder den Dateideskriptor nicht blockierend machen
oder via select (oder poll) nachschauen ob was drin steht wurde:
Beides führt zum ziel, nur auf älteren Linux systemen kann es sein das NONBLOCK auf STDIN nicht erlaubt ist (hat also keinen effekt).
Lustigerweise, windows ist ja auch ein posix system (oder war es früher mal), also müsste es auch die funktion select oder poll bereitstellen, man kann also eventuell irgendwie auch alles über den Unix weg machen, bin mir da aber nicht sicher. Meine bisherige implementierung tut ihren job
Die funktion ReadChar ließt also genau einen char aus den STDIN buffer, falls blocking false ist und nichts drin steht wird 0 zurückgegeben (da normalerweise in keinen String von nem echten terminal drin sein sollte, ist speziell auf nutzerinput ausgelegt).
Die funktion ReadSequence hingegen, ruft ReadChar so lange auf bis 0 zurückgegeben wird, ließt dir also nicht bis zur neuen zeile aus, sondern alles was bisher eingegeben wurde.
Beispiel:
Alles was der nutzer in den 10 sekunden des sleeps eingeben würde, würde auf einen schlag mit ReadSequence ausgelesen werden. Ich hab die funktion vor 2 jahren geschrieben, hatte gedacht das sie ReadLn funktionalität haben müsste, hab mich aber geirrt. Um es anders auszudrücken, ReadSequence gibt alles zurück was seit dem letzten ReadSequence gebuffert wurde.
Es löst gleichzeitig soweit ich weiß auch Escape codes auf, bin ich mir aber nicht mehr so sicher (immerhin ists schon 2 jahre her)
Zum thema multithreading und STDIN/STDOUT. Als dateien wird paralleler zugriff hier vom OS gehandelt, es ist also absolut kein problem aus verschiedenen Threads drauf zuzugreifen. Da das betriebsystem sogar buffert, kann man sogar konsistenten output erwarten, da alles was im buffer ist beim Flush in einem stück rausgeschrieben wird.
Im FPC wird alles was in einem Write steht gemeinsam gebuffert und danach geflusht.
Beispiel:
Das hat übrigens auch andere z.T. unerwünschte seiteneffekte, wenn man z.B. mehrere zeilen effizient schreiben will (also nicht das jedes mal dazwischen das konsolenfenster neu gezeichnet wird) muss man vorher alles in einen String schreiben und dann in einem write ausgeben
Was ich dir empfehlen würde, mach ne boolean variable stopped, lies in nem separaten thread mit ReadLn die eingabe ein, und setzt die variable auf true (eine schreiboperation mit einer konstanten ist atomar, da brauchst du nicht mal locks). In deinem Hauptloop schaust du dann, if stopped=true then exit; oder so. Ist am einfachsten, und funtkioniert auf allen betriebsystemen wie erwartet.
Meine funktion von oben ist viel zu kompliziert für das was du vorhast. Die funktion war für ein ncurses like zeicheninterface, welches dafür sorgen sollte das sowohl unter windows als auch linux man selbes verhalten vom STDIN und STDOUT erreichen kann.
Zum thema Synchronize nochmal, Synchronize ist für Event basierte programme (vor allem GUI anwendungen) entwickelt worde. CLI programme sind meist batch programme, da sind stink normale Locks und Critical sections meißt einfacher. Bzw. ich kam noch nie in die situation synchronize für non gui anwendungen zu brauchen. Denn mit synchronize musst du garantieren das die check funktion im main thread regelmäßig aufgerufen wird. Wenn z.B. der Main thread früher terminiert, und auf die worker warten muss, musst du da immer eine checking loop mit busy waiting implementieren, obwohl es nicht nötig ist. Außerdem, wenn du zu viel synchronize benutzt wird alles über den main thread gechannelt, was zu einem Bottleneck führen kann. z.B. will Thread 1 mit Thread 2 reden, warum zur hölle da den main thread mit reinziehen? Im worst case kann das zur zwangsserialisierung aller threads führen. Locks hingegen blockieren gar nix bis zwei threads auf die selbe resource zugreifen wollen, synchronized blockiert den main thread immer. (Dafür ist es deutlich einfacher mit locks deadlocks zu implementieren, was über synchronize natürlich nicht so einfach geht)
Angenommen wir sind unter Linux (windows macht das ganze ein bisschen anders und viel komplizierter aus, siehe die funktion von mir). Die eingabe und ausgabe von Programmen wird da als datei gehandhabt, STDIN und STDOUT (es gibt noch ne error ausgabe STDERR, die ignoriere ich aber mal gekonnt), mit dem FileDeskriptoren 0 und 1.
Wenn der nutzer was in die konsole eingibt wird das praktisch in den virtuellen File STDIN (0) geschrieben, und das wartet dann darauf gelesen zu werden. Wurde noch nichts reingeschrieben würde ein lese versuch so lange blockieren (also warten) bis etwas zum auslesen drin ist.
Wenn man jetzt nicht blockend arbeiten möchte, kann man entweder den Dateideskriptor nicht blockierend machen
Code: Alles auswählen
flags := FpFcntl(StdInputHandle, F_GetFl, 0);
FpFcntl(StdInputHandle, F_SetFl, flags or O_NONBLOCK);
Code: Alles auswählen
fpFD_ZERO(fdsin);
fpFD_SET(StdInputHandle, fdsin);
res := fpSelect(StdInputHandle + 1, @fdsin, nil, nil, 0); // timeout von 0 sekunden -> blockiert gar nicht
Lustigerweise, windows ist ja auch ein posix system (oder war es früher mal), also müsste es auch die funktion select oder poll bereitstellen, man kann also eventuell irgendwie auch alles über den Unix weg machen, bin mir da aber nicht sicher. Meine bisherige implementierung tut ihren job
Die funktion ReadChar ließt also genau einen char aus den STDIN buffer, falls blocking false ist und nichts drin steht wird 0 zurückgegeben (da normalerweise in keinen String von nem echten terminal drin sein sollte, ist speziell auf nutzerinput ausgelegt).
Die funktion ReadSequence hingegen, ruft ReadChar so lange auf bis 0 zurückgegeben wird, ließt dir also nicht bis zur neuen zeile aus, sondern alles was bisher eingegeben wurde.
Beispiel:
Code: Alles auswählen
sleep(10000);
WriteLn(ReadSequence(false));
Es löst gleichzeitig soweit ich weiß auch Escape codes auf, bin ich mir aber nicht mehr so sicher (immerhin ists schon 2 jahre her)
Zum thema multithreading und STDIN/STDOUT. Als dateien wird paralleler zugriff hier vom OS gehandelt, es ist also absolut kein problem aus verschiedenen Threads drauf zuzugreifen. Da das betriebsystem sogar buffert, kann man sogar konsistenten output erwarten, da alles was im buffer ist beim Flush in einem stück rausgeschrieben wird.
Im FPC wird alles was in einem Write steht gemeinsam gebuffert und danach geflusht.
Beispiel:
Code: Alles auswählen
WriteLn("Diese zeile wird ", "nicht gesplitted", ", da sie in einem rutsch geschrieben wird", " obwohl es mehrere writeln argumente sind");
Write('Diese zeile');
Write(' kann zwischen den Writes ');
WriteLn('unterbrochen werden');
Was ich dir empfehlen würde, mach ne boolean variable stopped, lies in nem separaten thread mit ReadLn die eingabe ein, und setzt die variable auf true (eine schreiboperation mit einer konstanten ist atomar, da brauchst du nicht mal locks). In deinem Hauptloop schaust du dann, if stopped=true then exit; oder so. Ist am einfachsten, und funtkioniert auf allen betriebsystemen wie erwartet.
Meine funktion von oben ist viel zu kompliziert für das was du vorhast. Die funktion war für ein ncurses like zeicheninterface, welches dafür sorgen sollte das sowohl unter windows als auch linux man selbes verhalten vom STDIN und STDOUT erreichen kann.
Zum thema Synchronize nochmal, Synchronize ist für Event basierte programme (vor allem GUI anwendungen) entwickelt worde. CLI programme sind meist batch programme, da sind stink normale Locks und Critical sections meißt einfacher. Bzw. ich kam noch nie in die situation synchronize für non gui anwendungen zu brauchen. Denn mit synchronize musst du garantieren das die check funktion im main thread regelmäßig aufgerufen wird. Wenn z.B. der Main thread früher terminiert, und auf die worker warten muss, musst du da immer eine checking loop mit busy waiting implementieren, obwohl es nicht nötig ist. Außerdem, wenn du zu viel synchronize benutzt wird alles über den main thread gechannelt, was zu einem Bottleneck führen kann. z.B. will Thread 1 mit Thread 2 reden, warum zur hölle da den main thread mit reinziehen? Im worst case kann das zur zwangsserialisierung aller threads führen. Locks hingegen blockieren gar nix bis zwei threads auf die selbe resource zugreifen wollen, synchronized blockiert den main thread immer. (Dafür ist es deutlich einfacher mit locks deadlocks zu implementieren, was über synchronize natürlich nicht so einfach geht)
Re: Thread in einer cli application
Danke für die ausführlichen Worte. Ich werde mir deine Funktionen noch etwas genauer anschauen, aber sie tut das was ich brauche auf sehr einfache Weise.
Ja in der ReadSequence() gibt es eine stelle an der an der geprüft wird ob das erste Zeichen ein "Escape" ist (#27), wenn nicht wird abgebrochen. Daher dachte ich man kann nur ein Zeichen auslesen.
Mit Sleep() zu arbeiten um auf eine Eingabe zu warten ist gar nicht gut. In dieser Zeit kann der User nicht "Exit" machen.
Das mit dem extra Worker-Thread für das auslesen des User-Inputs muss ich mir doch nochmal überlegen. Also ich kann besagten Thread starten und dort dann ein ReadLn() setzen, und halt solange warten bis der User was eingegeben hat und ENTER drückt.
Diesen Input könnte ich dann verarbeiten: Mal muss gleich was an Worker1.Thread geschickt werden und machmal was an den Main.Thread.
Wäre vielleicht schöner und sauberer.
Im normal Fall brauchen cli Apps keine aufwendigen Threads da eben kleine/einfache Aufgaben abgerabeitet werden sollen.
Aber der User soll hat den Exit-Befehl eingeben können und das kann ja nur der Main.Thread entgegen nehmen(bis jetzt). Der Main.Thread kann ja dann nicht einfach mal im Worker.Thread die var Exit:=true setzen, daher das Synchronize.
Es gibt eine sehr schicke cli app LeelaChess lc0,
https://github.com/LeelaChessZero/lc0/releases
welche die ganzen UCI Befehle entgegen nimmt werden des rechnens und immer fleissig output produziert.
So hoch komplex soll meine kleine cli app aber nicht werden.
Ja in der ReadSequence() gibt es eine stelle an der an der geprüft wird ob das erste Zeichen ein "Escape" ist (#27), wenn nicht wird abgebrochen. Daher dachte ich man kann nur ein Zeichen auslesen.
Mit Sleep() zu arbeiten um auf eine Eingabe zu warten ist gar nicht gut. In dieser Zeit kann der User nicht "Exit" machen.
Das mit dem extra Worker-Thread für das auslesen des User-Inputs muss ich mir doch nochmal überlegen. Also ich kann besagten Thread starten und dort dann ein ReadLn() setzen, und halt solange warten bis der User was eingegeben hat und ENTER drückt.
Diesen Input könnte ich dann verarbeiten: Mal muss gleich was an Worker1.Thread geschickt werden und machmal was an den Main.Thread.
Wäre vielleicht schöner und sauberer.
Im normal Fall brauchen cli Apps keine aufwendigen Threads da eben kleine/einfache Aufgaben abgerabeitet werden sollen.
Aber der User soll hat den Exit-Befehl eingeben können und das kann ja nur der Main.Thread entgegen nehmen(bis jetzt). Der Main.Thread kann ja dann nicht einfach mal im Worker.Thread die var Exit:=true setzen, daher das Synchronize.
Es gibt eine sehr schicke cli app LeelaChess lc0,
https://github.com/LeelaChessZero/lc0/releases
welche die ganzen UCI Befehle entgegen nimmt werden des rechnens und immer fleissig output produziert.
So hoch komplex soll meine kleine cli app aber nicht werden.