当 will/won 不 Python 暂停协程的执行?

When will/won't Python suspend execution of a coroutine?

当我在 cpython 3.6 上 运行 时,以下程序打印 hello world 一次,然后永远旋转。

作为旁注,取消注释 await asyncio.sleep(0) 行会导致它每秒打印 hello world,这是可以理解的。

import asyncio

async def do_nothing():
    # await asyncio.sleep(0)
    pass

async def hog_the_event_loop():
    while True:
        await do_nothing()

async def timer_print():
    while True:
        print("hello world")
        await asyncio.sleep(1)

loop = asyncio.get_event_loop()
loop.create_task(timer_print())
loop.create_task(hog_the_event_loop())
loop.run_forever()

这种行为(打印 hello world 一次)对我来说很有意义,因为 hog_the_event_loop 从不阻塞,因此不需要暂停执行。 我可以依赖这种行为吗? 当第 await do_nothing() 运行 行时,是否有可能不进入 do_nothing() 协程,执行实际上会暂停和恢复 timer_print(),导致程序第二次打印 hello world?

更笼统地说:python什么时候会暂停协程的执行并切换到另一个协程?是否可能 any 使用 await 关键字?还是仅在这导致基础 select 调用(例如 I/O、睡眠定时器等)的情况下?

补充说明

我明白,如果 hog_the_event_loop 看起来像这样,它肯定不会让出执行给另一个协程:

async def hog_the_event_loop():
    while True:
        pass

我正在尝试具体回答 await do_nothing() 是否与上述内容有任何不同的问题。

不,await do_nothing() 永远不会暂停。 await 传播 通过挂起周围协程作为响应从可等待对象中挂起。但是当 awaitable 已经准备好时,就没有什么可等待的了,并且从 await 开始继续执行(通常具有 return 值)。另一种思考“无需等待”的方式是,事件循环字面上 没有对象 可以作为从概念暂停中恢复时间的基础;甚至“在其他挂起的任务暂停后立即恢复”也是一个必须表示为某个对象的计划(例如sleep(0))。

一个什么也不做的协程总是准备就绪,就像一个睡觉的协程在时间过去后就准备好了。换句话说,一个只休眠 N 次的协程会暂停 N 次——即使 N 为 0。

This behavior (printing hello world a single time) makes sense to me, because hog_the_event_loop never blocks and therefore has no need to suspend execution. Can I rely on this behavior?

是的。此行为直接遵循 await 是如何由语言指定和实现的。将其更改为在等待对象本身没有暂停的情况下暂停肯定是一个重大更改,对于基于 async/await.

的 asyncio and other Python 库来说都是如此

Put more generally: When will python suspend execution of a coroutine and switch to another one? Is it potentially on any use of the await keyword?

从调用者的角度来看,任何 await 都可以 潜在地 挂起,由等待的对象(也称为 awaitable)自行决定。因此,特定 await 是否会挂起的最终决定取决于可等待对象(或者如果它是协程,则取决于等待自身的可等待对象,依此类推)。等待不选择挂起的可等待对象与 运行 普通 Python 代码相同 - 它不会将控制权传递给事件循环。

实际挂起的叶等待对象通常与 IO 就绪或超时事件有关,但并非总是如此。例如,等待 queue item will suspend if the queue is empty, and awaiting run_in_executor 将挂起,直到另一个线程中的函数 运行 完成。

值得一提的是asyncio.sleepexplicitly guaranteed暂停执行并推迟到事件循环,即使指定的延迟为0(在这种情况下它会立即恢复下一个事件循环传递)。