Python什么时候会/不会暂停协程的执行?

E_G*_*E_G 5 python coroutine python-3.x async-await python-asyncio

当我在 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()
Run Code Online (Sandbox Code Playgroud)

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

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

额外说明

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

async def hog_the_event_loop():
    while True:
        pass
Run Code Online (Sandbox Code Playgroud)

我试图专门解决是否await do_nothing()与上述不同的问题。

use*_*342 5

这种行为(一次打印 hello world)对我来说很有意义,因为hog_the_event_loop永远不会阻塞,因此不需要暂停执行。我可以依赖这种行为吗?

是的。此行为直接取决于await语言的指定和实现方式。对于 asyncio和 其他基于 async/await 的 Python 库来说,将其更改为挂起而不让可等待对象本身挂起肯定是一个重大更改。

更笼统地说:Python 什么时候会暂停协程的执行并切换到另一个协程?是否有可能使用该await关键字?

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

实际挂起的叶可等待事件通常与 IO 就绪或超时事件相关,但并非总是如此。例如,如果队列为空,则等待队列run_in_executor项目将挂起,而等待将挂起直到另一个线程中运行的函数完成。

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