Skip to content

实验 2:连续 await 和 gather

这一节先不讲限流,也不讲错误处理。只看一个最常见的差别:三个请求是一个接一个等,还是一起开始等。

运行:

bash
python3 examples/asyncio_demos/02_concurrent_sleep.py

示例里的 fetch()asyncio.sleep() 假装网络请求:

python
results = await asyncio.gather(
    fetch("profile", 0.4),
    fetch("orders", 0.2),
    fetch("recommendations", 0.3),
)

gather 适合什么

gather() 适合“这一批任务都要结果,全部结束后再往下走”的场景。它会把传入的 coroutine 安排成 Task,然后等待它们全部结束。

返回列表的顺序按传入顺序来,不按完成顺序来。下面这个例子里 orders 最先完成,但结果还是 profileordersrecommendations

总耗时接近 400ms,不是 900ms。因为三个等待同时开始了。

连续 await 为什么会变慢

如果你写成:

python
profile = await fetch("profile", 0.4)
orders = await fetch("orders", 0.2)
recommendations = await fetch("recommendations", 0.3)

第二个请求会等第一个完成后才开始。代码里有 asyncawait,但这段还是串行。

create_task 也能做到

你也可以显式创建 Task:

python
profile_task = asyncio.create_task(fetch("profile", 0.4))
orders_task = asyncio.create_task(fetch("orders", 0.2))
recs_task = asyncio.create_task(fetch("recommendations", 0.3))

profile = await profile_task
orders = await orders_task
recs = await recs_task

这也能并发,因为 Task 创建后就已经交给事件循环了。下一节会专门看这一点。

容易踩的坑

错误:

python
for url in urls:
    result = await fetch(url)
    results.append(result)

如果每个 fetch 互相独立,这会把并发写成串行。

如果 URL 数量很少,可以这样改:

python
results = await asyncio.gather(*(fetch(url) for url in urls))

先别急着把这段套到几千个 URL 上。数量很多时,要加并发上限。这个问题放到实验 4 单独讲。

小练习

  1. 把三个 delay 改成 1.00.10.1,观察总耗时。
  2. gather 改成连续 await,观察总耗时。
  3. 把示例改成 5 个 fetch(),确认总耗时接近最慢的那个等待。

面向学习目的的 Python asyncio 中文教程与 mini_asyncio 教学运行时。