我有一个 asyncio 程序,其中包含两个任务:
CLI 基本上是一个从连接到 stdin 的异步流中读取行的循环。
它可以工作,但不太舒服。问题是输入行不提供任何编辑命令,除了 [BACKSPACE ctrl-H] 和 [ctrl-U],这些命令是在 Linux 终端级别处理的,而不是在程序中处理的。我至少想要 [LEFT] 和 [RIGHT] 箭头和 [DEL]。
input()我尝试了内置导入功能,readline它能让我进行舒适的编辑,但它必须在单独的线程中运行,因为它是阻塞的(loop.run_in_executor)。
input()
readline
loop.run_in_executor
现在的问题是 [ctrl-C] 处理 ( KeyboardInterrupt)。任务被取消,但input()其自身线程中的 仍在等待 [ENTER] 键。只有在 [ENTER] 之后,应用程序才会退出。这太令人困惑了,并且是此方法的“阻止程序错误”。不幸的是,无法终止线程,因此我无法使 [ctrl-C] 按键正常终止程序。
KeyboardInterrupt
您是否知道如何解决该问题input(),或者您是否知道如何在 asyncio 任务中启用基本输入行编辑的另一种方法?
你遇到的问题是由于 input() 函数本身是阻塞式的,它会等待用户输入并在此期间阻止其他任务的执行。并且将 input() 放入单独的线程后,它不会很好地与 asyncio 协同工作,特别是当你按下 ctrl-C 时,主程序无法立即响应并结束。
asyncio
ctrl-C
为了让你的 asyncio 程序能够同时处理异步任务和支持命令行输入编辑,你需要使用异步版本的输入方法,而不是依赖于传统的 input() 函数。
aioconsole
aioconsole 是一个异步控制台输入库,它允许你在异步程序中进行命令行输入,并且提供了类似于 readline 的编辑功能。aioconsole 在底层使用了 asyncio 和 readline,并且与 asyncio 兼容。
你可以通过以下步骤来实现你的需求:
pip install aioconsole
下面是一个简单的示例,展示了如何在异步程序中使用 aioconsole 来实现带有编辑功能的命令行输入,同时保持异步任务的执行。
import asyncio import aioconsole # 任务1:做一些异步工作 async def task_1(): while True: print("任务 1 正在运行...") await asyncio.sleep(2) # 任务2:提供异步命令行界面 async def task_2(): while True: # 使用 aioconsole 异步读取用户输入,并允许编辑 user_input = await aioconsole.ainput("请输入命令: ") print(f"收到命令: {user_input}") # 将命令发送给任务1(这里仅做示范,实际可以调用其他处理逻辑) if user_input == "exit": print("退出程序...") break async def main(): # 启动任务1和任务2 task_1_task = asyncio.create_task(task_1()) task_2_task = asyncio.create_task(task_2()) # 等待任务结束 await asyncio.gather(task_1_task, task_2_task) if __name__ == "__main__": asyncio.run(main())
aioconsole.ainput(): 这个函数是 input() 的异步版本,能够在事件循环中非阻塞地读取用户输入。它提供了与传统 input() 类似的编辑功能,如 BACKSPACE、LEFT、RIGHT、DEL 等键(类似于 readline 的行为)。
aioconsole.ainput()
BACKSPACE
LEFT
RIGHT
DEL
task_1() 和 task_2(): 这两个任务分别执行一些异步工作和命令行输入的读取。task_2() 使用 ainput() 异步等待用户输入,当用户输入命令时,任务将处理这些命令并继续执行。
task_1()
task_2()
ainput()
await asyncio.gather(): 通过 asyncio.gather() 让多个异步任务并发执行,确保它们都在事件循环中得到调度和执行。
await asyncio.gather()
asyncio.gather()
aioconsole 库会正确地处理 KeyboardInterrupt,你可以按下 ctrl-C 来停止程序,并且它会在任务中立即响应,不会像 input() 那样阻塞。
如果你希望在程序中更好地处理 ctrl-C 或其他终止信号,可以通过捕获 KeyboardInterrupt 异常来清理资源,退出程序。例如:
try: asyncio.run(main()) except KeyboardInterrupt: print("程序已被终止。")