async/await 不等于自动并发,单个 await 是顺序等待,需用 asyncio.gather() 或 create_task() 显式并发;CPU 密集型任务应使用 ProcessPoolExecutor;未 await 可等待对象会导致逻辑跳过或静默失败;异步上下文须用 contextvars 而非 threading.local。

async/await 不等于自动并发
很多人以为只要把函数改成 async def,再用 await 调用,程序就会“变快”或“自动并行”。事实并非如此:单个 await 是顺序等待,不触发并发;只有显式调度多个协程(如用 asyncio.gather() 或 asyncio.create_task())才可能并发执行。
常见错误现象:
– 写了 5 个 await fetch_data() 连续调用,耗时接近 5 倍单次;
– 混淆 await asyncio.sleep(1) 和 time.sleep(1),后者会阻塞整个事件循环。
- 真正并发需用
asyncio.gather(task1(), task2(), task3())批量 await - 用
asyncio.create_task()提前启动协程,适合有依赖或需控制生命周期的场景 - CPU 密集型任务不能靠 async 加速,应改用
concurrent.futures.ProcessPoolExecutor
在同步代码里直接调用 async 函数会报错
比如在普通函数里写 result = my_async_func(),实际返回的是 coroutine 对象,不是结果;若不 await 或用 asyncio.run() 驱动,就只是个未执行的协程——后续一旦尝试打印或使用,大概率触发 RuntimeWarning: coroutine 'xxx' was never awaited。
使用场景常见于:单元测试、脚本快速验证、与老代码混用。
立即学习“Python免费学习笔记(深入)”;
- 调试时想临时跑一个 async 函数?用
asyncio.run(my_async_func()) - 在同步上下文中需要等结果?必须用
asyncio.run(),不能只调用不驱动 - 切勿在
__init__、@property、日志格式化等同步钩子里直接调 async 函数
忘记 await 可等待对象(如 Task、Future)导致逻辑跳过
asyncio.create_task() 返回的是 Task 对象,它本身是可等待的(awaitable),但如果不 await 它,协程就只是被调度、然后被丢弃——任务可能中途被取消,也可能静默失败,而主协程早已结束。
典型错误:
– task = asyncio.create_task(fetch_user()) 后没 await task;
– 用 asyncio.ensure_future() 但没收集返回值或 await;
– 在 try/except 外层漏掉 await,导致异常无法被捕获。
- 所有通过
create_task()、ensure_future()、to_thread()启动的可等待对象,都应明确 await 或加入gather - 若需“发完即忘”,至少加
asyncio.current_task().get_loop().create_task(...)并确保 loop 不提前关闭 - 用
asyncio.wait_for(task, timeout=...)包一层,避免无限挂起
误用 threading.local 在异步上下文中失效
很多开发者习惯用 threading.local() 存储请求上下文(如用户 ID、trace_id),但在 asyncio 中,协程可能在不同线程间切换(尤其用了 loop.run_in_executor),且单线程内多个协程共享同一 thread-local,导致数据污染或丢失。
表现症状:
– A 请求的 trace_id 意外出现在 B 请求日志中;
– local.var = 'x' 后,在另一个 await 点取不到值;
– 使用 contextvars 前,用 threading.local 实现的中间件在压测下出错。
- 异步上下文隔离请用
contextvars.ContextVar,它是 asyncio 原生支持的 -
ContextVar必须在协程开始时 set(如中间件入口),并在每个 await 分界点后仍有效 - 不要试图在
run_in_executor的子线程里读写主线程的ContextVar,需手动传递值










