You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python aiohttp循环内await create_task同步阻塞原因咨询

asyncio任务执行差异核心原理

两种写法的行为差异本质

asyncio.create_task()的核心作用是将传入的协程包装为Task实例,立即加入当前事件循环的待调度队列,但事件循环只有在当前运行的协程主动让出执行权(即触发await)时,才会切换去执行队列中的其他待调度任务。

为什么循环内逐个await create_task()表现为同步阻塞

对应代码片段:

for link in urls:
    await asyncio.create_task(fetch_(link))

执行流程逐轮拆解:

  • 进入第一轮循环:调用create_task将第一个URL的请求协程包装为Task加入调度队列,紧接着就执行await等待这个刚创建的Task完成。
  • 当前blockmain协程此时让出执行权,事件循环开始运行第一个请求任务。当任务运行到网络IO等待点(session.get()、response.text()的await位置)时,事件循环会尝试切换其他待运行任务——但此时调度队列中没有其他请求任务:剩余URL对应的Task还没被创建,循环还没走到下一轮,根本没有被加入队列。
  • 事件循环只能等待第一个请求的IO操作完成、整个Task执行结束,才会把执行权交还给blockmain协程,进入下一轮循环。
  • 后续每一轮循环都重复上述逻辑:创建一个Task、立刻等待它执行完、再创建下一个。整个过程中事件循环同一时间最多只有一个网络请求任务在运行,表现和同步阻塞完全一致。

为什么先批量创建Task再await能实现并发

对应代码片段:

tasks = [asyncio.create_task(fetch_(link)) for link in urls]
for t in tasks:
    await t

执行流程拆解:

  • 首先同步执行列表推导式:遍历所有URL,将每一个请求协程都包装为Task,全部加入事件循环的待调度队列。这一步没有任何await操作,不会让出执行权,只是快速完成所有任务的注册。
  • 所有任务全部提交完成后,才进入循环逐个执行await。
  • 第一次await第一个Task时,blockmain让出执行权,事件循环开始调度队列中所有已提交的请求任务:哪个任务的IO就绪就运行哪个,碰到IO等待就切换到下一个就绪任务,所有网络请求同时处于IO等待、被调度的状态。
  • 即使按顺序逐个await任务,在等待第一个任务完成的过程中,其他已提交的任务早已在事件循环中被调度执行,等await后续任务时,不少任务可能已经执行完毕,最终呈现出并发执行的效果。

关键认知纠正:await本身不会创造并发,它的作用只是等待一个可等待对象执行完成。并发成立的核心前提是多个任务已经提前被提交到事件循环的调度队列中,这样协程让出执行权时,事件循环才有其他任务可以切换执行。

如果需要简化并发写法,可以直接使用asyncio.gather(),效果和手动创建任务列表再逐个await完全一致:

await asyncio.gather(*[fetch_(link) for link in urls])

gather会自动将传入的所有协程包装为Task提交到事件循环,再等待所有任务执行完成。


内容的提问来源于stack exchange,提问作者garyHuang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 20:09:35