1

I have a multi-threaded Windows console app whose control thread runs a user input loop like this:

char c;
do {
    cin >> c;
    // Alter activity based on c
} while(c != 'q')
// Tell other threads to close, .join(), and do cleanup

However at a certain time I want the program itself to be able to gracefully quit. The most obvious way to do so would be to put a "q\n" onto the stdin stream. Is there a reasonable way to do that?

Or a good alternative for a callback to force exit the main control loop (on the primary thread) so that the program falls through to the subsequent cleanup methods?

(The closest I have found so far is this which requires spawning a child process and seems like kludgy overkill at best.)

Community
  • 1
  • 1
feetwet
  • 3,248
  • 7
  • 46
  • 84
  • 1
    Install a [signal handler](http://stackoverflow.com/questions/1641182/how-can-i-catch-a-ctrl-c-event-c) and handle CTRL-C events. – Captain Obvlious Jul 13 '15 at 21:13
  • Add another flag, and poll standard input asynchronously? Use POSIX signals? Use Windows messages and other native Windows functions? Use some other [synchronization](https://en.wikipedia.org/wiki/Synchronization_%28computer_science%29)? – Some programmer dude Jul 13 '15 at 21:14
  • Get ready for reading - good starting point is https://msdn.microsoft.com/en-us/library/windows/desktop/ms644945%28v=vs.85%29.aspx – Ed Heal Jul 13 '15 at 21:14
  • 2
    Or use `ReadConsole()` or `ReadConsoleInfo()` instead of `cin`, then you can create an event object using `CreateEvent()` and use `WaitForMultipleObjects()` to wait on console input and the event object at the same time. The result of the wait will tell you which one is signaled. Then you can signal the event when you need to terminate the loop, and read from the console only when there is something ready to be read. – Remy Lebeau Jul 13 '15 at 21:15
  • I decided the idea from @CaptainObvlious looked most straightforward. Of course it turned out to be more complicated than expected, but you can judge for yourself: implementation is in my answer below. – feetwet Jul 13 '15 at 23:46

3 Answers3

1

You can use another flag as loop break condition for all of your threads. The problem with interthread-communication can be solved by synchronization objects which came with C++11 like atomics or mutex.

Youka
  • 2,646
  • 21
  • 33
  • Is doing something like `while (!(atomic)done) std::this_thread::yield();` considered efficient for the main thread? – feetwet Jul 13 '15 at 22:25
  • 2
    For this kind of job, there's [std::condition_variable](http://en.cppreference.com/w/cpp/thread/condition_variable). – Youka Jul 13 '15 at 22:34
0

I ended up using the suggestion from @Captain Obvlious, but even that was tricky (thanks Windows!): As soon as a control signal is handled it causes all sorts of problems with the unchecked char input loop. However, with the following additions any thread can gracefully end the program by calling GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, 0);

bool exiting = false;
bool cleanedUp = false;
void Cleanup() {
    if(!cleanedUp) cleanedUp = true;
    else return;
    // Tell other threads to close, .join(), and do cleanup
}

// https://msdn.microsoft.com/en-us/library/ms683242(v=vs.85).aspx
bool ConsoleControl(DWORD dwCtrlType) {
    exiting = true; // This will cause the stdin loop to break
    Cleanup();
    return false;
}

int main() {
    SetConsoleCtrlHandler((PHANDLER_ROUTINE) ConsoleControl, true);
    .
    .
    .
    // Main user control loop
    char c;
    do {
        cin >> c;
        if(exiting) break;
        if(c < 0) continue; // This must be a control character
        // Alter activity based on c
    } while(c != 'q')
    Cleanup();
    return 0;
}
feetwet
  • 3,248
  • 7
  • 46
  • 84
0

This is an old question but I stumbled upon it and ended up solving the original problem.

I used the WriteConsoleInput function to simulate a user entering keystrokes to the console. This code sample is based off of This question.

HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
DWORD dwTmp;
INPUT_RECORD ir[2];

ir[0].EventType = KEY_EVENT;
ir[0].Event.KeyEvent.bKeyDown = TRUE;
ir[0].Event.KeyEvent.dwControlKeyState = 0;
ir[0].Event.KeyEvent.uChar.UnicodeChar = 'q';
ir[0].Event.KeyEvent.wRepeatCount = 1;
ir[0].Event.KeyEvent.wVirtualKeyCode = 'Q';
ir[0].Event.KeyEvent.wVirtualScanCode = MapVirtualKey('Q', MAPVK_VK_TO_VSC);

ir[1].EventType = KEY_EVENT;
ir[1].Event.KeyEvent.bKeyDown = TRUE;
ir[1].Event.KeyEvent.dwControlKeyState = 0;
ir[1].Event.KeyEvent.uChar.UnicodeChar = VK_RETURN;
ir[1].Event.KeyEvent.wRepeatCount = 1;
ir[1].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
ir[1].Event.KeyEvent.wVirtualScanCode = MapVirtualKey(VK_RETURN, MAPVK_VK_TO_VSC);

WriteConsoleInput(hStdin, ir, 2, &dwTmp);

On my system MAPVK_VK_TO_VSC wasn't defined so I used 0 instead.

You should add error checking where appropriate.

I hope this helps.

Adam Edry
  • 1
  • 1