无需等待输入即可输入?

Posted

技术标签:

【中文标题】无需等待输入即可输入?【英文标题】:Cin without waiting for input? 【发布时间】:2015-02-19 08:38:42 【问题描述】:

对于我正在进行的项目,我需要程序能够接收用户的输入,但是当他们输入内容时,程序可以继续循环。

例如:
while (true)

    if (userInput == true)
    
        cin >> input
    
    //DO SOMETHING

这意味着//DO SOMETHING 将在每个循环中发生,而无需用户按回车一百万次。

之前,我的解决方案是使用 conio.h 中的 kbhit()getch() 创建自己的输入,但这变得非常混乱,出于可移植性等原因,我不喜欢使用 conio.h。此外,它不需要特别要使用cin,因为它很有可能无法使用它,所以任何不需要我自己使用“不太好的”库进行输入的好的解决方案,将不胜感激。

【问题讨论】:

也许您需要某种异步 I/O?喜欢 epoll 或 kqueue 或 libevent2? 您希望在用户按下第一个键时,还是在输入完成时停止循环? 基本上,我根本不希望输入影响程序,因此循环是连续的,但是系统每隔一段时间就会以输入的形式获取新数据来处理 Non-blocking console input C++ 的可能重复项 【参考方案1】:

为此研究多线程可能是值得的。我通常不愿建议它,因为多线程会引入许多最终难以调试的潜在问题,但在这种情况下,它们可以很容易地隔离。我设想这样的事情:

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>

int main() 
  std::atomic<bool> interrupted;
  int x;
  int i = 0;

  do 
    interrupted.store(false);

    // create a new thread that does stuff in the background
    std::thread th([&]() 
        while(!interrupted) 
          // do stuff. Just as an example:
          std::cout << i << std::flush;
          std::this_thread::sleep_for(std::chrono::milliseconds(100));
        
      );

    std::cin >> x;

    // when input is complete, interrupt thread and wait for it to finish
    interrupted.store(true);
    th.join();

    // apply x, then loop to make new thread to do stuff
    i = x;
   while(x != -1); // or some other exit condition

乍一看,这看起来有点浪费,因为它不断产生和丢弃线程,但用户输入在计算方面需要一个永恒的时间,因此开销不应过高。更重要的是,它确实具有避免任何数据竞争的优势,因为主(输入)循环和后台线程之间的唯一通信方式是原子中断标志,并且x 应用于共享数据发生当没有线程正在运行可以与主循环竞争时。

【讨论】:

虽然这个解决方案看起来很有希望,但根据其他一些地方,gcc 不支持 std::thread。除非我的编译器设置错误(如果有人可以提供帮助,那就是 Code::Blocks) gcc 已经支持std::thread 有一段时间了;我在这里用 Linux 下的 gcc 4.9 对其进行了测试。但是如果你需要支持非常老的 gcc,总是有Boost.Thread. 标准库线程主要基于它,所以它的工作方式几乎相同。编辑:也许您必须配置代码块以通过 g++ -std=c++11 选项? @Ben 你使用了 -std=c++11 编译选项吗? @Christophe 是的,事实上我在编译器设置中尝试了所有 3 个选项 @Wintermute 我从 OP 那里了解到,即使用户没有输入某些输入,他也想做一些事情。哇,为什么要在每次迭代中重新创建 theread,而不是在开始循环之前启动它?【参考方案2】:

免责声明:以下内容似乎适用于 Linux 上的 gcc,但由于某些原因,它不适用于 Windows 上的 VC++。规范似乎为这里的实现提供了很大的余地,VC++ 肯定会利用它......

在任何std::basic_istream 或其底层std::basic_streambuf 上都有多个可用的功能。

为了知道是否有任何字符可供输入,您可以在std::basic_streambuf上致电in_avail

if (std::cin.rdbuf() and std::cin.rdbuf()->in_avail() >= 0) 

in_avail 为您提供没有阻塞的可用字符数,如果没有这样的字符,它将返回-1。之后,您可以使用常规的“格式化”读取操作,例如std::cin &gt;&gt; input

否则,对于未格式化的读取,您可以使用std::basic_istream 中的readsome,它会返回最多 N 个可用字符而不会阻塞:

size_t const BufferSize = 512;
char buffer[BufferSize];

if (std::cin.readsome(buffer, BufferSize) >= 1) 

但是要注意,这个方法的实现是高度可变的,所以对于一个可移植的程序来说它可能没有那么有用。

注意: 如评论中所述,in_avail 方法可能参差不齐。我confirm it can work,但是您首先必须使用 C++ IO 流的一个不起眼的功能:std::ios_base::sync_with_stdio(false),它允许 C++ 流缓冲输入(从而从 C 的 stdio 缓冲区中窃取它)。

【讨论】:

这不能移植。使用 gcc 的 libstdc++,std::cin.rdbuf()-&gt;in_avail() 总是返回 0(除非你在自己的流缓冲区中交换)。 没有一个解决方案适用于 Visual Studio:in_avail() 始终为真,readsome() 始终返回 0。 @Wintermute:我想说“有趣”,但在这一点上它甚至都不好笑,但我应该考虑一下。对于in_avail,问题是通过使用sync_with_stdio 解决的,我将其添加为答案。 @Christophe:我什至不确定我想知道为什么,你可以试试 VC++ 上的sync_with_stdio 技巧(我没有),它可能会解决问题... @MatthieuM。 Désollé,但它仍然不起作用!如果没有可用的字符,它总是返回 0。如果我将测试更改为 &gt;0 而不是 &gt;=0,即使我输入了一些内容,它仍然是 0。似乎只要用户没有请求输入,可用字符的数量就不会更新。我不是参差不齐,但我观察到它不是便携式解决方案。【参考方案3】:

很遗憾,没有简单的便携式方法来异步检查是否按下了键。但我猜标准委员会已经仔细评估了利弊。

如果您不想依赖第三方事件管理库,并且如果多线程过于繁琐,另一种选择可能是拥有自己的 kbhit() 版本,并针对您想要支持的环境进行条件编译:

如果您的 conio.h 支持 kbhit(),请使用它。 windows可以参考_kbhit() 对于 linux 和 posix,您可以使用 Matthieu 的答案,或者查看 here 以获得 Morgan Mattews 的代码

这不是最学术的答案,但它是务实的。

【讨论】:

【参考方案4】:

或许,你可以试试:

while (true)

    if (userInput == true)
    
        if(cin >> input)
        else
            std::cout << "Invalid input!" << std::endl;
            cin.clear();
        
    
    //DO SOMETHING

【讨论】:

以上是关于无需等待输入即可输入?的主要内容,如果未能解决你的问题,请参考以下文章

无需等待输入就可以获取字符

在某些部分禁用等待时间:setTimeout

PHP CLI:如何从 TTY 读取单个字符的输入(无需等待回车键)?

Firebase 手机认证无需等待验证码

C# HttpClient 无需等待响应即可发布信息

React Native 测试 - 无需等待即可行动