小能豆

如何在 asyncio 任务中获得舒适的(例如 GNU-readline 样式)输入行?

py

我有一个 asyncio 程序,其中包含两个任务:

  • 任务 1 做了一些工作
  • 任务 2 提供命令行界面 (CLI),它读取用户的命令并将其发送到任务 1 进行处理

CLI 基本上是一个从连接到 stdin 的异步流中读取行的循环。

它可以工作,但不太舒服。问题是输入行不提供任何编辑命令,除了 [BACKSPACE ctrl-H] 和 [ctrl-U],这些命令是在 Linux 终端级别处理的,而不是在程序中处理的。我至少想要 [LEFT] 和 [RIGHT] 箭头和 [DEL]。

input()我尝试了内置导入功能,readline它能让我进行舒适的编辑,但它必须在单独的线程中运行,因为它是阻塞的(loop.run_in_executor)。

现在的问题是 [ctrl-C] 处理 ( KeyboardInterrupt)。任务被取消,但input()其自身线程中的 仍在等待 [ENTER] 键。只有在 [ENTER] 之后,应用程序才会退出。这太令人困惑了,并且是此方法的“阻止程序错误”。不幸的是,无法终止线程,因此我无法使 [ctrl-C] 按键正常终止程序。

您是否知道如何解决该问题input(),或者您是否知道如何在 asyncio 任务中启用基本输入行编辑的另一种方法?


阅读 24

收藏
2024-12-04

共1个答案

小能豆

你遇到的问题是由于 input() 函数本身是阻塞式的,它会等待用户输入并在此期间阻止其他任务的执行。并且将 input() 放入单独的线程后,它不会很好地与 asyncio 协同工作,特别是当你按下 ctrl-C 时,主程序无法立即响应并结束。

为了让你的 asyncio 程序能够同时处理异步任务和支持命令行输入编辑,你需要使用异步版本的输入方法,而不是依赖于传统的 input() 函数。

解决方案:使用 aioconsole

aioconsole 是一个异步控制台输入库,它允许你在异步程序中进行命令行输入,并且提供了类似于 readline 的编辑功能。aioconsole 在底层使用了 asyncioreadline,并且与 asyncio 兼容。

你可以通过以下步骤来实现你的需求:

  1. 安装 aioconsole
pip install aioconsole
  1. 使用 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())

解释:

  1. aioconsole.ainput() 这个函数是 input() 的异步版本,能够在事件循环中非阻塞地读取用户输入。它提供了与传统 input() 类似的编辑功能,如 BACKSPACELEFTRIGHTDEL 等键(类似于 readline 的行为)。

  2. task_1()task_2() 这两个任务分别执行一些异步工作和命令行输入的读取。task_2() 使用 ainput() 异步等待用户输入,当用户输入命令时,任务将处理这些命令并继续执行。

  3. await asyncio.gather() 通过 asyncio.gather() 让多个异步任务并发执行,确保它们都在事件循环中得到调度和执行。

解决 ctrl-C 问题:

aioconsole 库会正确地处理 KeyboardInterrupt,你可以按下 ctrl-C 来停止程序,并且它会在任务中立即响应,不会像 input() 那样阻塞。

如果你希望在程序中更好地处理 ctrl-C 或其他终止信号,可以通过捕获 KeyboardInterrupt 异常来清理资源,退出程序。例如:

try:
    asyncio.run(main())
except KeyboardInterrupt:
    print("程序已被终止。")

总结:

  • 使用 aioconsole 库可以让你在 asyncio 程序中处理命令行输入,并提供类似 readline 的编辑功能(包括 LEFTRIGHTDEL 等键)。
  • aioconsole 是非阻塞的,能够和 asyncio 协同工作,不会阻塞主事件循环。
  • 通过 asyncio 的异步任务,你可以在不阻塞其他任务的情况下,持续读取命令并执行相应的逻辑。
2024-12-04