多线程控制台输入输出

11

我在使用多线程应用程序中的控制台。目前,它只接受输出(如printf等),到目前为止我没有任何问题。然而,我想要支持控制台输入,并且这就是我的问题所在。

预先警告,我对处理控制台输入和输出的更复杂的细节非常不熟悉。 我对该主题的经验不超过printf / cout,scanf / cin以及使用SetConsoleTextAttribute()更改颜色(在Windows上)。

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

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

我的第一个问题是,当我键入某些输入时,任何输出都会显示在我的输入中间。我想要处理此问题的解决方案是将控制台的底部一行保留为输入,并将输出发送到倒数第二行,将输入行向下推。我该如何做到这一点?

3个回答

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" 清除其内容,并在用户键入任何内容时替换为更新的内容。它支持一切你期望从一个简单的多线程终端中得到的能够输入和接收多个线程输出的功能。


2
你真的不想尝试保留控制台的一部分作为输入,同时写入到控制台的其余部分。至少,如果你只是写滚动文本的话,这样做是可能的,但会充满错误和麻烦,而且不值得。参见异步控制台输出,了解一些问题的提示。
当然,仅使用conio.h无法完成这个任务。
你可以分配两个控制台屏幕缓冲区,一个用于输入,一个用于程序输出。当程序正常运行时,选择输出屏幕缓冲区,你将在屏幕上看到输出在滚动。但当程序等待用户输入时,你交换屏幕缓冲区,以便输出仍在进行,但在另一个屏幕缓冲区中进行。
你最终需要自己格式化输出并调用WriteConsoleOutput,将要写入的屏幕缓冲区的句柄传递给它。这很快变得复杂,而且很难做到完美。我知道我以前在这上面花费了太多时间,而且总是会出现奇怪的问题。
我不会说你想做的事情不可能。但我可以说,你会遇到很多困难。

这个想法是多个线程同时写入控制台输出,而另一个线程正在等待输入。我只想避免冲突。问题在于“当程序正常运行”和“当程序等待用户输入”两者同时发生,一直都在发生。现在我正在研究pdcurses。 - Haydn V. Harach
@HaydnV.Harach:你可以让输出线程始终写入控制台。然后,当用户按下键时,您可以交换输出缓冲区,并将输出暂停(实际上是隐藏),直到用户完成输入,或者直到某个超时时间到期(因此,如果用户没有按Enter键就离开了,输出将恢复)。这可能不是完美的解决方案,但它可能有效。 - Jim Mischel
我很愿意付出额外的努力,以确保任务得到适当的完成。目前我正在使用pdcurses,但遇到了一些问题,主要是如何滚动文本(当前它会写到窗口底部,然后放弃),以及如何在屏幕上同时显示两个“窗口”,每个窗口都接收输出(一个用于实际输出信息,另一个用于回显输入)。 - Haydn V. Harach
投反对票?通常需要留下评论解释回答有何问题。 - Jim Mischel

1

我已经在使用conio.h(按任意键继续非常方便)。我希望输入命令键入的字符被回显,但我不希望输出消息发生在它们的中间。 - Haydn V. Harach
请查看第一个链接,它展示了如何禁用按键时的回显。 - erapert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接