【问题标题】:C++ write to stdin (in Windows)C++ 写入标准输入(在 Windows 中)
【发布时间】:2015-10-02 07:36:56
【问题描述】:

我有一个多线程的 Windows 控制台应用程序,其控制线程运行这样的用户输入循环:

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

但是在某个时间我希望程序本身能够优雅地退出。最明显的方法是将"q\n" 放到标准输入流中。有没有合理的方法来做到这一点?

或者作为回调强制退出主控制循环(在主线程上)以便程序进入后续清理方法的好选择?

(到目前为止,我发现的最接近的是this,它需要生成一个子进程,充其量看起来像是笨拙的矫枉过正。)

【问题讨论】:

  • 安装 signal handler 并处理 CTRL-C 事件。
  • 添加另一个标志,并异步轮询标准输入?使用 POSIX 信号?使用 Windows 消息和其他原生 Windows 功能?使用其他synchronization
  • 或者使用ReadConsole()或者ReadConsoleInfo()代替cin,那么你可以使用CreateEvent()创建一个事件对象并使用WaitForMultipleObjects()等待控制台输入和事件对象在同时。等待的结果会告诉你哪个是信号。然后,您可以在需要终止循环时发出事件信号,并仅在有内容可供读取时从控制台读取。
  • 我认为@CaptainObvlious 的想法看起来最直接。当然,结果比预期的要复杂,但你可以自己判断:实现在我下面的答案中。

标签: c++ multithreading process stdin


【解决方案1】:

这是一个老问题,但我偶然发现它并最终解决了最初的问题。

我使用WriteConsoleInput 函数来模拟用户在控制台中输入按键。此代码示例基于 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);

在我的系统上,MAPVK_VK_TO_VSC 没有定义,所以我改用了0

您应该在适当的地方添加错误检查。

我希望这会有所帮助。

【讨论】:

    【解决方案2】:

    我最终使用了@Captain Obvlious 的建议,但即使这样也很棘手(感谢 Windows!):一旦处理了控制信号,就会导致未经检查的 char 输入循环出现各种问题。但是,通过以下添加,任何线程都可以通过调用 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;
    }
    

    【讨论】:

      【解决方案3】:

      您可以使用另一个标志作为所有线程的循环中断条件。线程间通信的问题可以通过 C++11 附带的同步对象来解决,例如 atomicsmutex

      【讨论】:

      • 对主线程执行类似while (!(atomic&lt;bool&gt;)done) std::this_thread::yield(); 的操作是否被认为是有效的?
      • 对于这种工作,有std::condition_variable
      猜你喜欢
      • 2012-04-25
      • 1970-01-01
      • 1970-01-01
      • 2013-06-12
      • 2012-08-24
      • 2012-01-25
      • 2015-09-19
      • 1970-01-01
      • 2021-04-09
      相关资源
      最近更新 更多