[gelöst]Thread in einer cli application

Rund um die LCL und andere Komponenten
hubblec4
Beiträge: 341
Registriert: Sa 25. Jan 2014, 17:50

[gelöst]Thread in einer cli application

Beitrag von hubblec4 »

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?
Zuletzt geändert von hubblec4 am Mo 10. Jun 2019, 02:58, insgesamt 1-mal geändert.

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Thread in einer cli application

Beitrag von Warf »

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:

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;


Für die Uses benötigst du:

Code: Alles auswählen

uses ...
  {$IfDef UNIX}
  , BaseUnix, termio
  {$Else}
  , Windows
{$EndIf} ;


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

mschnell
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

Beitrag von mschnell »

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

hubblec4
Beiträge: 341
Registriert: Sa 25. Jan 2014, 17:50

Re: Thread in einer cli application

Beitrag von hubblec4 »

mschnell hat geschrieben:CheckSynchronize() ist OK.
Es muss im Mainthread aufgerufen werden.
Der Mainthread darf auf nichts warten.


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.

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.

hubblec4
Beiträge: 341
Registriert: Sa 25. Jan 2014, 17:50

Re: Thread in einer cli application

Beitrag von hubblec4 »

@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?

hubblec4
Beiträge: 341
Registriert: Sa 25. Jan 2014, 17:50

Re: Thread in einer cli application

Beitrag von hubblec4 »

Ich habe nun auch alles auf Synchronize() mit Hilfe von CheckSynchronize() umgestellt und komischerweise geht es jetzt aufeinmal.

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;
 


"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?

hubblec4
Beiträge: 341
Registriert: Sa 25. Jan 2014, 17:50

Re: Thread in einer cli application

Beitrag von hubblec4 »

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


Kann der Worker-Thread einfach WritLn ausführen und auf die Console schreiben? Das stört den Main.Thread dann nicht?

Socke
Lazarusforum e. V.
Beiträge: 3158
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

Beitrag von Socke »

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?

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:Kann der Worker-Thread einfach WritLn ausführen und auf die Console schreiben? Das stört den Main.Thread dann nicht?

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.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1430
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

Beitrag von fliegermichl »

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.

mschnell
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

Beitrag von mschnell »

hubblec4 hat geschrieben:Aber bei der cli ist klar, da gibts keine GUI.

Genau das habe ich damals gemacht. (Ein Widget-Type ohne GUI.)
Es funktioniert.
-Michael

mschnell
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

Beitrag von mschnell »

hubblec4 hat geschrieben:Kann der Worker-Thread einfach WritLn ausführen und auf die Console schreiben? Das stört den Main.Thread dann nicht?

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.
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

mschnell
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

Beitrag von mschnell »

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.

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.
-Michael

hubblec4
Beiträge: 341
Registriert: Sa 25. Jan 2014, 17:50

Re: Thread in einer cli application

Beitrag von hubblec4 »

Socke hat geschrieben:
hubblec4 hat geschrieben:Kann der Worker-Thread einfach WritLn ausführen und auf die Console schreiben? Das stört den Main.Thread dann nicht?

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.


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.


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.


Ja das dachte ich mir schon, denn es knallte als ich das ohne Synchronize probiert hatte.


mschnell hat geschrieben:
hubblec4 hat geschrieben:Aber bei der cli ist klar, da gibts keine GUI.

Genau das habe ich damals gemacht. (Ein Widget-Type ohne GUI.)
Es funktioniert.
-Michael


Mmh wenn du magst kann ich mir deine Arbeit mal anschauen, wo finde ich diese? Kenne mich mit "Widget" nicht so aus.



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.

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Thread in einer cli application

Beitrag von Warf »

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

Code: Alles auswählen

    flags := FpFcntl(StdInputHandle, F_GetFl, 0);
    FpFcntl(StdInputHandle, F_SetFl, flags or O_NONBLOCK);

oder via select (oder poll) nachschauen ob was drin steht wurde:

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

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:

Code: Alles auswählen

sleep(10000);
WriteLn(ReadSequence(false));

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:

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');

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)

hubblec4
Beiträge: 341
Registriert: Sa 25. Jan 2014, 17:50

Re: Thread in einer cli application

Beitrag von hubblec4 »

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.

Antworten