我想知道当事件循环切换任务时python会提供什么保证。
据我所知async/await从线程显著不同之处在于基于时间分片事件循环不切换任务,这意味着除非任务收益率(await),它会无限期地进行。这实际上可能是有用的,因为在异步下管理关键部分比使用线程要容易得多。
async
await
我不太清楚的是类似以下内容:
async def caller(): while True: await callee() async def callee(): pass
在这个例子中caller是反复的await。因此从技术上讲,它正在屈服。但是我尚不清楚这是否将允许事件循环上的其他任务执行,因为它只会callee屈服于且永远不会屈服。
caller
callee
也就是说,callee即使我知道它不会阻塞也要在“关键区域”内等待,我是否有发生其他意外事件的风险?
您警惕是正确的。caller从产生callee,并产生事件循环。然后,事件循环决定要恢复的任务。(希望)在的两次调用之间可以压缩其他任务callee。callee需要等待实际的阻塞,Awaitable例如asyncio.Future或asyncio.sleep(), 而不是 协程,否则控件将不会返回到事件循环,直到caller返回为止。
Awaitable
asyncio.Future
asyncio.sleep()
例如,以下代码将在caller2任务开始工作之前完成caller1任务。由于callee2本质上是一个同步功能,无需等待阻塞的I / O操作,因此,不会创建任何挂起点,并且caller2会在每次调用之后立即恢复callee2。
caller2
caller1
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),也会发生任务切换,并且任务将同时运行。
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代码,这是有意义的。
asyncio
还有一点要注意的是:这仍然有效,如果callee等待协同程序,反过来,等待一个asyncio.Future,asyncio.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)