为何直接await表达式与先赋值再await的异步执行效果不同?
为什么
parallel_fail的执行效果和consecutive一致,而非parallel? 先看三个函数的核心差异:
输出对比
consecutive和parallel_fail的输出(串行执行):
SLEEP for 2.5 seconds # 0秒时打印 back after 2.5 seconds # 2.5秒时打印 SLEEP for 2 seconds # 2.5秒时打印 back after 2 seconds # 4.5秒时打印
parallel的输出(并行执行):
SLEEP for 2.5 seconds # 0秒时打印 SLEEP for 2 seconds # 0秒时打印 back after 2 seconds # 2秒时打印 back after 2.5 seconds # 2.5秒时打印
核心原因分析
1. asyncio.create_task()的作用
调用asyncio.create_task(coro)时,会立即把传入的协程coro提交给事件循环,让它开始执行(只要事件循环没有被其他任务占用)。
2. 三个函数的执行逻辑
consecutive:
先await f(2.5)——这会暂停当前协程,直到f(2.5)完全执行完毕(包括2.5秒的睡眠),才会执行下一行await f(2),全程串行。parallel:
先执行a = asyncio.create_task(f(2.5)),此时f(2.5)已经启动(打印第一行);紧接着执行b = asyncio.create_task(f(2)),f(2)也启动(打印第二行)。最后await a和await b只是等待两个已经在运行的任务结束,所以两个协程并行,总耗时2.5秒。parallel_fail:await asyncio.create_task(f(2.5))——这里创建任务后立刻就开始等待这个任务完成,事件循环被这个await占用,直到2.5秒后f(2.5)执行完毕,才会继续执行下一行await asyncio.create_task(f(2))。全程没有机会让两个任务同时运行,自然和串行效果一致。
为什么await something不等价于a = something; await a?
这是因为两者的执行时机差异:
- 当你写
a = asyncio.create_task(f(2.5))时,任务已经被提交到事件循环并启动,之后你可以在await a之前做其他操作(比如启动第二个任务)。 - 而
await asyncio.create_task(f(2.5))是创建任务后立刻进入等待状态,当前协程被挂起,事件循环只能专注于执行这个任务,直到它完成,根本没有间隙去启动第二个并行任务。
换句话说,后者相当于把“创建任务”和“等待任务完成”绑定成了一个不可分割的步骤,中间没有机会触发其他任务的执行。
示例代码
import asyncio async def f(n): print(f'SLEEP for {n} seconds') await asyncio.sleep(n) print(f'back after {n} seconds') async def consecutive(): print('consecutive:') await f(2.5) await f(2) async def parallel(): print('parallel:') a = asyncio.create_task(f(2.5)) b = asyncio.create_task(f(2)) await a await b async def parallel_fail(): print('parallel fail:') await asyncio.create_task(f(2.5)) await asyncio.create_task(f(2)) asyncio.run(consecutive()) print('----------------------') asyncio.run(parallel()) print('----------------------') asyncio.run(parallel_fail())
内容的提问来源于stack exchange,提问作者Watchduck
相关产品推荐
相关产品推荐

