2026-02-28
asyncio 爬坑记录
Python 的 asyncio 折腾了两天,总算把事件循环、协程、async/await 这些概念理清楚了。记录一下学习和踩坑的过程。
为什么需要 asyncio
IO 密集型任务(网络请求、文件读写)等待的时候 CPU 是空闲的。异步编程让 CPU 在等待的时候去处理其他任务,提高效率。
核心概念
事件循环(Event Loop):程序的指挥中心,负责调度所有任务。
协程(Coroutine):可以被暂停和恢复的函数,用 async def 定义。
Future:代表还未完成的任务结果。
基本用法
import asyncio
async def hello():
print('Hello')
await asyncio.sleep(1) # 模拟 IO 操作
print('World')
# 运行
asyncio.run(hello())
await 的坑
最大的坑:忘了 await
# 错误写法:函数不会执行
result = asyncio.sleep(1) # 这只是个协程对象,没执行
# 正确写法
await asyncio.sleep(1) # 加 await 才会真正执行
我之前就是忘了加 await,导致代码完全不按预期运行。
gather vs wait
并发执行多个任务:
# gather:等待所有任务完成,返回结果列表
results = await asyncio.gather(task1(), task2(), task3())
# wait:可以设置条件,返回完成的、未完成的
done, pending = await asyncio.wait(
[task1(), task2(), task3()],
return_when=asyncio.FIRST_COMPLETED # 第一个完成就返回
)
用 gather 更简单,wait 更灵活。
实际应用
并发请求多个 API:
async def fetch_all(urls):
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
return await asyncio.gather(*tasks)
async def fetch(session, url):
async with session.get(url) as resp:
return await resp.json()
常见错误
- 在同步函数里直接调用 async 函数:同步函数不能 await 协程
- 事件循环已关闭:asyncio.run() 只能用一次,用 run_until_complete 可以复用
- 死锁:await 顺序不对可能导致死锁
总结
asyncio 适合 IO 密集型任务,不适合 CPU 密集型任务(用 multiprocessing)。
理解事件循环的工作原理很重要,不然会踩很多坑。