【问题标题】:Multithreaded console I/O多线程控制台 I/O
【发布时间】:2014-03-10 22:49:45
【问题描述】:

我在我的多线程应用程序中使用控制台。现在,它只接受输出(printf 等),到目前为止我没有任何问题。但是,我也希望能够支持控制台输入,这就是我的生活变得复杂的地方。

预先警告,我非常不熟悉使用控制台输入和输出的更复杂的细微差别。我在这方面的经验并没有比 printf/cout、scanf/cin 和使用 SetConsoleTextAttribute() 更改颜色(在 windows 上)更进一步。

我希望我的程序尽可能地交叉兼容,但我不反对必须编写特定于平台的代码,只要我能找到其他平台的可行替代方案。

从概念上讲,我希望控制台在它自己的线程上运行,这样它就可以在等待 cin 时锁定,而不会冻结整个程序或其他线程之一。任何线程都可以将控制台输出发送到该线程,该线程将以干净的方式输出它(可能使用线程安全队列),并且控制台读取的任何输入都会将命令发送到适当的线程。

我的第一个问题是,当我输入一些输入时,任何输出都会显示在我输入的中间。我想处理的解决方案是保留控制台的底线以供输入,并将输出转到倒数第二行,将输入线向下推。我该怎么做?

【问题讨论】:

    标签: c++ multithreading console


    【解决方案1】:

    您真的不想在写入控制台的其余部分时尝试保留部分控制台以供输入。至少,如果您只是编写滚动文本,则不会。这是可能,但充满了错误和比它的价值更多的麻烦。有关问题的一些提示,请参阅Async Console Output

    当然,仅使用 conio.h 是不可能做到这一点的。

    您可以分配两个控制台屏幕缓冲区,一个用于输入,一个用于程序输出。当您的程序正常运行时,会选择输出屏幕缓冲区,并且您会看到输出在屏幕上滚动。但是当您的程序等待用户输入时,您会交换屏幕缓冲区,以便输出仍在继续,但在另一个屏幕缓冲区中。

    您最终必须自己格式化输出并调用WriteConsoleOutput,将您要写入的屏幕缓冲区的句柄传递给它。它真的很匆忙变得复杂,而且很难做对。如果可能的话。我知道我过去在这方面花费了太多时间,而且总是出现奇怪的问题。

    我不会说你想做的事是不可能的。不过,我会说,你会很难过。

    【讨论】:

    • 这个想法是多个线程正在写入控制台输出,而另一个线程正在等待输入。我只是想避免冲突。问题是“当你的程序正常运行时”和“当你的程序正在等待用户输入时”都是同时发生的。现在我正在研究 pdcurses。
    • @HaydnV.Harach:您可以让输出线程始终写入控制台。然后,您可以在用户按下某个键时交换输出缓冲区,并让输出暂停(实际上是隐藏的),直到用户完成输入,或者直到某个超时到期(因此,如果用户在没有按 Enter 的情况下离开,输出将恢复) .这可能是一个不完美的解决方案,但它可以工作。
    • 我可以通过额外的工作来正确完成它。现在我正在使用 pdcurses,但我遇到了几个绊脚石,即如何让文本滚动(现在它一直写到它到达窗口底部,然后放弃),以及如何获得两个“窗口”同时显示在屏幕上,每个接收输出(一个用于实际输出消息,一个用于回显输入)。
    • 投反对票?习惯上留下评论来解释答案有什么问题。
    【解决方案2】:

    嗯,我使用 pdcurses 解决了它。如果其他人想做类似的事情,我就是这样做的。首先,我这样初始化控制台:

    Console::Console(bool makeConsole)
    {
        if (makeConsole == false)
            return;
    
        if (self)
            throw ("You only need one console - do not make another!\n");
        self = this;
    
    #ifdef WIN32
        AllocConsole();
    #endif
        initscr();
    
        inputLine = newwin(1, COLS, LINES - 1, 0);
        outputLines = newwin(LINES - 1, COLS, 0, 0);
    
        if (has_colors())
        {
            start_color();
            for (int i = 1; i <= COLOR_WHITE; ++i)
            {
                init_pair(i, i, COLOR_BLACK);
            }
        }
        else
            wprintw(outputLines, "Terminal cannot print colors.\n");
    
        scrollok(outputLines, TRUE);
        scrollok(inputLine, TRUE);
    
        leaveok(inputLine, TRUE);
        nodelay(inputLine, TRUE);
        cbreak();
        noecho();
        keypad(inputLine, TRUE);
    
        initCommands();
    
        hello("Starting %s.\n", APP_NAME);
        hellomore("Version %i.%i.%i.\n\n", APP_MAJORVER, APP_MINORVER, APP_REVISION);
    }
    

    接下来,这是负责处理输出的函数。它实际上非常简单,我不需要做任何特别的事情来保持线程安全。我可能根本没有遇到任何问题,但一个简单的解决方法是在它上面加上一个互斥锁。

    void Console::sendFormattedMsg(short prefixColor, const char* prefix, short color, const char* format, ...)
    {
        if (!self)
            return;
    
        va_list args;
        va_start(args, format);
    
        if (has_colors())
        {
            if (prefix)
            {
                wattron(outputLines, A_BOLD | COLOR_PAIR(prefixColor));
                wprintw(outputLines, prefix);
            }
    
            if (color == COLOR_WHITE)
                wattroff(outputLines, A_BOLD);
            wattron(outputLines, COLOR_PAIR(color));
            vwprintw(outputLines, format, args);
    
            wattroff(outputLines, A_BOLD | COLOR_PAIR(color));
        }
        else
        {
            wprintw(outputLines, prefix);
            vwprintw(outputLines, format, args);
        }
    
        wrefresh(outputLines);
        va_end(args);
    }
    

    最后,输入。这需要相当多的微调。

    void Console::inputLoop(void)
    {
        static string input;
    
        wattron(inputLine, A_BOLD | COLOR_PAIR(COLOR_WHITE));
        wprintw(inputLine, "\n> ");
        wattroff(inputLine, A_BOLD | COLOR_PAIR(COLOR_WHITE));
    
        wprintw(inputLine, input.c_str());
        wrefresh(inputLine);
    
        char c = wgetch(inputLine);
        if (c == ERR)
            return;
    
        switch (c)
        {
        case '\n':
            if (input.size() > 0)
            {
                sendFormattedMsg(COLOR_WHITE, "> ", COLOR_WHITE, input.c_str());
                cprint("\n");
    
                executeCommand(&input[0]);
                input.clear();
            }
            break;
    
        case 8:
        case 127:
            if (input.size() > 0) input.pop_back();
            break;
    
        default:
            input += c;
            break;
        }
    }
    

    这是从处理窗口消息的同一线程运行的每一帧。我使用nodelay() 禁用了wgetch() 的阻塞行为,从而无需在它自己的线程中运行控制台输入。我还禁用回显并手动回显输入。在输入窗口上启用滚动允许我使用简单的“\n”清除它的内容,如果用户输入了任何内容,则将其替换为更新的内容。它支持一个简单的多线程终端所期望的一切,能够输入输入以及接收来自多个线程的输出。

    【讨论】:

      【解决方案3】:

      要禁用回显字符,请查看以下内容: Reading a password from std::cin

      也许将它与this guy's blog post on non-blocking Win32 console io结合起来。

      您可能还会发现这些东西很有用: conio.h, pdcurses

      【讨论】:

      • 我已经在使用 conio.h(按任意键继续非常方便)。我确实希望为输入命令输入的字符被回显,我只是不希望输出消息发生在它们中间。
      • 检查第一个链接,它显示了如何禁用回显按下的字符。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-25
      • 1970-01-01
      • 2011-06-20
      • 2012-06-02
      相关资源
      最近更新 更多