await 总是给其他任务一个执行的机会吗?

Phi*_*ing 8 python async-await

我想知道当事件循环切换任务时,python 提供了什么保证。

据我所知async/await从线程显著不同之处在于基于时间分片事件循环不切换任务,这意味着除非任务收益率(await),它会无限期地进行。这实际上很有用,因为在 asyncio 下管理临界区比使用线程更容易。

我不太清楚的是以下内容:

async def caller():
    while True:
        await callee()


async def callee():
    pass
Run Code Online (Sandbox Code Playgroud)

在这个例子中caller是重复的await。所以从技术上讲,它正在屈服。但是我不清楚这是否会允许事件循环上的其他任务执行,因为它只callee屈服于并且永远不会屈服。

也就是说,如果我callee在“关键部分”内等待,即使我知道它不会阻塞,我是否有发生其他意外事件的风险?

Hur*_*ful 10

你保持警惕是对的。caller产生于callee,并产生于事件循环。然后事件循环决定恢复哪个任务。其他任务可能(希望)被挤压在对 的调用之间calleecallee需要等待实际阻塞,Awaitable例如asyncio.Futureor asyncio.sleep()而不是协程,否则控制将不会返回到事件循环直到caller返回。

例如,以下代码将caller2在开始处理任务之前完成caller1任务。因为callee2本质上是一个同步函数,无需等待阻塞的 I/O 操作,因此,不会创建暂停点,并且caller2会在每次调用callee2.

import asyncio
import time

async def caller1():
    for i in range(5):
        await callee1()

async def callee1():
    await asyncio.sleep(1)
    print(f"called at {time.strftime('%X')}")

async def caller2():
    for i in range(5):
        await callee2()

async def callee2():
    time.sleep(1)
    print(f"sync called at {time.strftime('%X')}")

async def main():
    task1 = asyncio.create_task(caller1())
    task2 = asyncio.create_task(caller2())
    await task1
    await task2

asyncio.run(main())

Run Code Online (Sandbox Code Playgroud)

结果:

sync called at 19:23:39
sync called at 19:23:40
sync called at 19:23:41
sync called at 19:23:42
sync called at 19:23:43
called at 19:23:43
called at 19:23:44
called at 19:23:45
called at 19:23:46
called at 19:23:47
Run Code Online (Sandbox Code Playgroud)

但是如果callee2 awaits如下,即使 awaits 也会发生任务切换asyncio.sleep(0),并且任务会并发运行。

sync called at 19:23:39
sync called at 19:23:40
sync called at 19:23:41
sync called at 19:23:42
sync called at 19:23:43
called at 19:23:43
called at 19:23:44
called at 19:23:45
called at 19:23:46
called at 19:23:47
Run Code Online (Sandbox Code Playgroud)

结果:

called at 19:22:52
sync called at 19:22:52
called at 19:22:53
sync called at 19:22:53
called at 19:22:54
sync called at 19:22:54
called at 19:22:55
sync called at 19:22:55
called at 19:22:56
sync called at 19:22:56
Run Code Online (Sandbox Code Playgroud)

这种行为不一定直观,但考虑到它asyncio是为了同时处理 I/O 操作和网络,而不是通常的同步 Python 代码,这是有道理的。

还有一点要注意的是:这仍然有效,如果callee等待协同程序,反过来,等待一个asyncio.Futureasyncio.sleep()或另一个协程说的那些东西环比下滑的await之一。当Awaitable等待阻塞时,流控制将返回到事件循环。所以以下也有效。

async def callee2():
    await inner_callee()
    print(f"sync called at {time.strftime('%X')}")

async def inner_callee():
    await asyncio.sleep(1)
Run Code Online (Sandbox Code Playgroud)