Non-blocking input



  • Ich möchte bei meinem Programm gerne Eingaben vom Benutzer über die Konsole erhalten und währenddessen Berechnungen durchführen. Da alle Lese-Operationen blockieren (d.h. die Funktion kehrt nicht zurück solange nicht Enter gedrückt wurde), habe ich mich entschlossen, das Problem mit einem Eingabe-Thread zu lösen, der dann so aussehen könnte:

    int communicate() {
    	string operation;
    	do {
    		cin >> operation;
    	} while( operation != "quit");
    
    	return 0;
    }
    

    Das wäre auch die beste Lösung, wäre da nicht das Problem am Ende der Programmausführung. Wenn die Berechnungen durchgeführt worden sind, soll sich das Programm automatisch schließen. Da ich den Eingabe-Thread nicht "gewaltsam" terminieren möchte, muss der Haupt-Thread die "quit"-Eingabe selber machen.

    Gibt es dafür einen Weg, der nicht auf platform-spezifischen Erweiterungen basiert? Und wenn nicht, wie könnte man das mit der WinAPI lösen?

    Ich habe schon versucht, den cin-StreamBuffer aus dem normalen Thread heraus zu befüllen (hatte da aber auch meine Bedenken, da ich bezweifle, dass die istream-Klasse thread-safe ist). Dies funktionierte allerdings nicht.



  • Vielleicht wäre es da günstiger, nicht die eingaben in einen Extra-Thread auszulagern, sondern die Berechnungen 😉

    Was die Synchronisation angeht: Ansi C++ bietet da nicht wirklich etwas an (selbst Multi-Threading an sich geht schon über den Horizont des Standards hinaus) - und bei jeder MT-Bibliothek sollten auc Sync-Objekte wie Events, Mutexe oder CriticalSections mitgeliefert sein, um zwischen verschiedenen Threads zu kommunizieren.



  • CStoll schrieb:

    Vielleicht wäre es da günstiger, nicht die eingaben in einen Extra-Thread auszulagern, sondern die Berechnungen 😉

    Was die Synchronisation angeht: Ansi C++ bietet da nicht wirklich etwas an (selbst Multi-Threading an sich geht schon über den Horizont des Standards hinaus) - und bei jeder MT-Bibliothek sollten auc Sync-Objekte wie Events, Mutexe oder CriticalSections mitgeliefert sein, um zwischen verschiedenen Threads zu kommunizieren.

    Im Grunde genommen ist es egal, wie rum ich die Sache aufteile (Wenn die Berechnungen in einem Worker-Thread fertig sind, wartet der Hauptthread ja immer noch auf Input). Für Threads verwende ich übrigens die Boost.Thread-Library.



  • Hm.
    Ich würde mal behaupten das geht nicht, zumindest nicht protabel. Mit Win32 könnte was möglich sein - ich weiss aber nicht wie.

    Wieder ein weiteres Beispiel dafür dass die iostreams sinnlos und schlecht ist 🙂



  • So, ich habe jetzt eine Lösung über die WinAPI gefunden. Und zwar schicke ich mit Hilfe der WriteConsoleInput-Funktion die quit-Message an mein eigenes Programm, sodass der auf Input wartende Thread beendet werden kann. Am Ende sieht das dann so aus:

    const size_t numCharacters = 5;
    wstring quitMessage = L"quit\r";
    WORD virtualKeyCodes[numCharacters] = { VK_Q, VK_U, VK_I, VK_T, VK_RETURN };
    
    const size_t numRecords = numCharacters * 2;
    INPUT_RECORD inputRecord[numRecords];
    memset(inputRecord, 0, sizeof(inputRecord));
    
    for(size_t i = 0; i < numCharacters; ++i) {
    	size_t index = i * 2;
    
    	inputRecord[index].EventType = KEY_EVENT;
    	inputRecord[index].Event.KeyEvent.bKeyDown = TRUE;
    	inputRecord[index].Event.KeyEvent.dwControlKeyState = 0;
    	inputRecord[index].Event.KeyEvent.uChar.UnicodeChar = quitMessage[i];
    	inputRecord[index].Event.KeyEvent.wRepeatCount = 1;
    	inputRecord[index].Event.KeyEvent.wVirtualKeyCode = virtualKeyCodes[i];
    	inputRecord[index].Event.KeyEvent.wVirtualScanCode = MapVirtualKey(virtualKeyCodes[i], MAPVK_VK_TO_VSC);
    
    	memcpy(&inputRecord[index + 1], &inputRecord[index], sizeof(INPUT_RECORD));
    	inputRecord[index + 1].Event.KeyEvent.bKeyDown = FALSE;
    }
    
    HANDLE stdIn = GetStdHandle(STD_INPUT_HANDLE);
    
    DWORD numRecordsWritten;
    WriteConsoleInput(stdIn, inputRecord, numRecords, &numRecordsWritten);
    

    Eine andere Lösung, die ich auch in Erwägung gezogen habe, wäre mit Hilfe der kbhit-Funktion auf Eingaben zu warten. Allerdings hat sich das als nicht brauchbar herausgestellt. Ich rufe das Programm teilweise auch über ein anderes Programm auf und leite dann stdin und stdout um. Wenn ich nun in das umgeleitete stdin schreibe, bekommt die kbhit-Funktion dies nicht mit.
    Deshalb habe ich mich für die Lösung mit WriteConsoleInput entschlossen. Kennt noch jemand eine andere, einfachere Lösung?

    Evtl. wäre es jetzt angebracht, den Thread ins WinAPI-Forum zu verschieben.



  • Hm.
    Ich würde eher ein Flag setzen welches dem "Console-Input" Thread mitteilt "was auch immer du gerade bekommen hast, ignorier es und beende dich", und dann einfach nur ein "Enter" schicken.

    Sonst kann es (schätze ich - ausprobiert hab' ich es nicht) passieren dass du statt "quit" z.B. "ququit" reinliest, nämlich wenn der User gerade dabei war "quit" einzutippen (und nur bis "u" gekommen ist bevor dein worker Thread "quit" geschickt hat)...



  • Dieser Thread wurde von Moderator/in HumeSikkins aus dem Forum C++ in das Forum DOS und Win32-Konsole verschoben.

    Im Zweifelsfall bitte auch folgende Hinweise beachten:
    C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?

    Dieses Posting wurde automatisch erzeugt.



  • hustbaer schrieb:

    Hm.
    Ich würde eher ein Flag setzen welches dem "Console-Input" Thread mitteilt "was auch immer du gerade bekommen hast, ignorier es und beende dich", und dann einfach nur ein "Enter" schicken.

    Sonst kann es (schätze ich - ausprobiert hab' ich es nicht) passieren dass du statt "quit" z.B. "ququit" reinliest, nämlich wenn der User gerade dabei war "quit" einzutippen (und nur bis "u" gekommen ist bevor dein worker Thread "quit" geschickt hat)...

    Da hast du recht, den Flag habe ich jetzt hinzugefügt, damit dieser Fall nicht mehr auftreten kann.

    Ich hatte mir noch eine anderen Ansatz überlegt, der etwas einfacher wäre:

    HANDLE stdIn = GetStdHandle(STD_INPUT_HANDLE);
    char buffer[] = "quit";
    DWORD bytesWritten;
    BOOL written = WriteFile(stdIn, buffer, sizeof(buffer), &bytesWritten, NULL);
    HRESULT error = GetLastError();
    

    Allerdings wird als Fehler gemeldet, dass das übergebene Handle ungültig sei. Kann sich jemand vorstellen, woran das liegt?


Anmelden zum Antworten