运行器

源代码: Lib/asyncio/runners.py

本节概述了用于运行 asyncio 代码的高级 asyncio 原语。

它们建立在事件循环之上,旨在简化常见广泛场景下的异步代码使用。

运行 asyncio 程序

asyncio.run(coro, *, debug=None, loop_factory=None)

在 asyncio 事件循环中执行 coro 并返回结果。

该参数可以是任何 awaitable 对象。

此函数运行 awaitable,负责管理 asyncio 事件循环,完成异步生成器,并关闭执行器。

当同一线程中运行另一个 asyncio 事件循环时,不能调用此函数。

如果 debugTrue,事件循环将以调试模式运行。False 明确禁用调试模式。None 用于遵循全局的调试模式设置。

如果 loop_factory 不是 None,它将用于创建新的事件循环;否则使用 asyncio.new_event_loop()。循环在结束时关闭。此函数应作为 asyncio 程序的主要入口点使用,理想情况下应只调用一次。建议使用 loop_factory 来配置事件循环,而不是策略。传递 asyncio.EventLoop 允许在没有策略系统的情况下运行 asyncio。

执行器被赋予 5 分钟的超时时间来关闭。如果执行器未在该时间内完成,则会发出警告并关闭执行器。

示例

async def main():
    await asyncio.sleep(1)
    print('hello')

asyncio.run(main())

在 3.7 版本加入。

3.9 版本中变更: 更新为使用 loop.shutdown_default_executor()

3.10 版本中变更: debug 默认为 None,以遵循全局调试模式设置。

3.12 版本中变更: 添加了 loop_factory 参数。

3.14 版本中变更: coro 可以是任何 awaitable 对象。

备注

asyncio 策略系统已弃用,并将在 Python 3.16 中移除;从那时起,将需要显式的 loop_factory 来配置事件循环。

运行器上下文管理器

class asyncio.Runner(*, debug=None, loop_factory=None)

一个上下文管理器,它简化了同一上下文中的多个异步函数调用。

有时,几个顶级异步函数应在同一个事件循环contextvars.Context中调用。

如果 debugTrue,事件循环将以调试模式运行。False 明确禁用调试模式。None 用于遵循全局的调试模式设置。

loop_factory 可用于覆盖循环创建。由 loop_factory 负责将创建的循环设置为当前循环。默认情况下,如果 loop_factoryNone,则使用 asyncio.new_event_loop() 并使用 asyncio.set_event_loop() 将其设置为当前事件循环。

基本上,asyncio.run() 示例可以通过使用运行器来重写

async def main():
    await asyncio.sleep(1)
    print('hello')

with asyncio.Runner() as runner:
    runner.run(main())

在 3.11 版本中新增。

run(coro, *, context=None)

在嵌入的事件循环中执行 coro

该参数可以是任何 awaitable 对象。

如果参数是协程,它将被封装在一个任务中。

一个可选的仅关键字 context 参数允许为要运行的代码指定自定义的 contextvars.Context。如果 context 为 None,则使用运行器的默认上下文。

返回 awaitable 的结果或引发异常。

当同一线程中运行另一个 asyncio 事件循环时,不能调用此函数。

3.14 版本中变更: coro 可以是任何 awaitable 对象。

close()

关闭运行器。

完成异步生成器,关闭默认执行器,关闭事件循环并释放嵌入的 contextvars.Context

get_loop()

返回与运行器实例关联的事件循环。

备注

Runner 使用惰性初始化策略,其构造函数不初始化底层低级结构。

嵌入的 loopcontextwith 语句体进入时或首次调用 run()get_loop() 时创建。

处理键盘中断

在 3.11 版本中新增。

当通过 Ctrl-C 触发 signal.SIGINT 时,默认情况下主线程会引发 KeyboardInterrupt 异常。然而,这不适用于 asyncio,因为它可能中断 asyncio 内部机制并导致程序无法退出。

为了解决这个问题,asyncio 按如下方式处理 signal.SIGINT

  1. asyncio.Runner.run() 在任何用户代码执行之前安装自定义的 signal.SIGINT 处理器,并在函数退出时将其移除。

  2. 为了执行传入的协程,Runner 会为其创建一个主任务。

  3. 当通过 Ctrl-C 引发 signal.SIGINT 时,自定义信号处理器会通过调用 asyncio.Task.cancel() 来取消主任务,这会在主任务内部引发 asyncio.CancelledError。这会导致 Python 栈展开,try/excepttry/finally 块可用于资源清理。主任务取消后,asyncio.Runner.run() 会引发 KeyboardInterrupt

  4. 用户可能会编写一个无法被 asyncio.Task.cancel() 中断的紧密循环,在这种情况下,第二次连续的 Ctrl-C 将立即引发 KeyboardInterrupt,而不会取消主任务。