小编典典

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

python

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

据我所知async/await从线程显著不同之处在于基于时间分片事件循环不切换任务,这意味着除非任务收益率(await),它会无限期地进行。这实际上可能是有用的,因为在异步下管理关键部分比使用线程要容易得多。

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

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


async def callee():
    pass

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

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


阅读 123

收藏
2021-01-16

共1个答案

小编典典

您警惕是正确的。caller从产生callee,并产生事件循环。然后,事件循环决定要恢复的任务。(希望)在的两次调用之间可以压缩其他任务calleecallee需要等待实际的阻塞,Awaitable例如asyncio.Futureasyncio.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())

结果:

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

但是,如果按以下方式callee2 等待 ,则即使它等待asyncio.sleep(0),也会发生任务切换,并且任务将同时运行。

async def callee2():
    await asyncio.sleep(1)
    print('sync called')

结果:

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

这种行为不一定是直观的,但考虑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)
2021-01-16