提问人:djvg 提问时间:1/2/2021 最后编辑:djvg 更新时间:1/8/2023 访问量:802
使用选择器的非阻塞 STDIN
non-blocking stdin using selectors
问:
玩弄非阻塞控制台输入,将 Python 的选择器
与 结合使用,有一些我不明白的地方:sys.stdin
假设我想在用户按下时退出循环,可能是在先输入其他一些字符之后。Enter
如果我执行阻塞读取,如下所示,则该过程始终在遇到第一个换行符后完成,正如预期的那样,无论前面的任何字符如何:\n
import sys
character = ''
while character != '\n':
character = sys.stdin.read(1)
现在考虑以下非阻塞读取的最小化示例:
import sys
import selectors
selector = selectors.DefaultSelector()
selector.register(fileobj=sys.stdin, events=selectors.EVENT_READ)
character = ''
while character != '\n':
for key, __ in selector.select(timeout=0):
character = key.fileobj.read(1)
如果我点击作为第一个输入,则会生成一个换行符,并且该过程将按预期完成。Enter
但是,如果我先输入一些其他字符,然后输入 ,则该过程不会完成:我需要再次点击才能完成。EnterEnter
显然,这种实现仅在换行符是第一个输入时才有效。
这可能是有充分理由的,但我目前没有看到它,也找不到任何相关问题。
这是否与我的非阻塞实现有关,或者是缓冲区问题,或者可能与控制台或终端实现有关?stdin
(我正在从 ubuntu 上的 python 3.8 shell 运行它。
答:
sys.stdin
是 IO 的一个实例。TextIOWrapper
,它反过来包装 io 的实例。BufferedReader
。
添加到代码的末尾,让您看看发生了什么:print(repr(character))
$ python foo.py
# enter asd\n
'a'
# enter \n
's'
'd'
'\n'
当您第一次输入“asd\n”时,python 会将其全部读取到缓冲区中,但只返回第一个字符。由于基础 stdin 现在是空的,因此下一次调用将再次阻塞。当您输入另一个换行符时,选择“取消阻止”,代码将继续从缓冲区读取字符,直到到达第一个“\n”。selector.select()
可以使用 绕过缓冲。这给出了预期的行为(请注意,它返回字节而不是字符串):os.read(key.fileobj.fileno(), 1)
$ python foo.py
# enter asd\n
b'a'
b's'
b'd'
b'\n'
编辑:关于cbreak模式的一些背景
从:man 3 cbreak
通常,tty 驱动程序会缓冲键入的字符,直到键入换行符或回车符。cbreak 例程禁用行缓冲和擦除/终止字符处理(中断和流控制字符不受影响),使用户键入的字符立即可供程序使用。nocbreak 例程将终端返回到正常(熟)模式。
在 cbreak 模式下,字符一次发送一个,因此 python 没有机会缓冲输入:
$ python foo.py
# enter a
'a'
# enter s
's'
# enter d
'd'
# enter \n
'\n'
评论
selector.select()
timeout=0
os.read(key.fileobj.fileno(), 1)
os.read(key.fd, 1)
timeout=0
read(1)
<enter>
select
select
<enter>
下一个:Linux FD 向用户公开等待
评论
cbreak
selectors
sys.stdin.buffer
<enter>