首页 > 解决方案 > 可以取消的异步键盘输入

问题描述

我正在尝试使用它编写一个并发 Python 程序asyncio,它也接受键盘输入。当我尝试关闭我的程序时出现问题。由于键盘输入最终完成sys.stdin.readline,因此该函数仅在我按下后才返回ENTER,无论我stop()是事件循环还是cancel()函数的Future.

有没有办法提供asyncio可以取消的键盘输入?

这是我的 MWE。它将接受键盘输入 1 秒钟,然后stop()

import asyncio
import sys

async def console_input_loop():
    while True:
        inp = await loop.run_in_executor(None, sys.stdin.readline)
        print(f"[{inp.strip()}]")

async def sleeper():
    await asyncio.sleep(1)
    print("stop")
    loop.stop()

loop = asyncio.get_event_loop()
loop.create_task(console_input_loop())
loop.create_task(sleeper())
loop.run_forever()

标签: pythonpython-asyncio

解决方案


问题是执行者坚持要确保在程序终止时所有正在运行的期货都已完成。但在这种情况下,您实际上想要一个“不干净”的终止,因为没有可移植的方式来取消正在进行的read()sys.stdin异步访问。

取消未来没有任何效果,因为concurrent.futures.Future.cancel一旦它的回调开始执行,它就是一个空操作。避免不必要的等待的最好方法是首先避免run_in_executor并产生自己的线程:

async def ainput():
    loop = asyncio.get_event_loop()
    fut = loop.create_future()
    def _run():
        line = sys.stdin.readline()
        loop.call_soon_threadsafe(fut.set_result, line)
    threading.Thread(target=_run, daemon=True).start()
    return await fut

该线程是手动创建的并标记为“守护进程”,因此在程序关闭时没有人会等待它。结果,使用ainput而不是run_in_executor(sys.stdin.readline)按预期终止的代码变体:

async def console_input_loop():
    while True:
        inp = await ainput()
        print(f"[{inp.strip()}]")

# rest of the program unchanged

推荐阅读