应使用 asyncio.sleep() 而非 time.sleep(),因后者会阻塞整个事件循环,而前者是协作式挂起并交还控制权;漏写 await、在非 async 函数或无事件循环环境中调用均会导致错误。

time.sleep() 会直接冻结整个事件循环
它不是“暂停当前协程”,而是让当前线程休眠,而 asyncio 的事件循环就跑在这个线程里。一旦调用 time.sleep(1),所有协程、IO 就绪检查、定时器、心跳全部停摆——连刚用 asyncio.create_task() 启动的任务都不会执行一帧。
常见现象:asyncio.sleep(0.01) 能让其他任务轮到一次调度,但 time.sleep(0.01) 仍会强制阻塞至少一个完整调度周期,尤其在高负载下可能跳过多个 ready 协程。
asyncio.sleep() 是事件循环的显式协作点
它不休眠线程,而是把当前协程标记为 suspended,并立刻把控制权交还给事件循环。此时事件循环可以继续处理 socket 就绪、执行回调、调度其他 task。
关键行为差异:
-
await asyncio.sleep(2.5)立即返回,协程挂起,2.5 秒后被重新标记为 ready - 漏写
await:只得到一个<coroutine object asyncio.sleep at></coroutine>,什么也不发生 - 在非 async 函数里调用:触发
RuntimeWarning: coroutine 'sleep' was never awaited - 不在事件循环中运行(比如普通脚本没包进
asyncio.run()):报RuntimeError: no running event loop
哪些地方最容易偷偷混进 time.sleep()
危险位置往往不在主流程,而在逻辑胶水层:
- 日志重试逻辑里:比如
while not success: do_request(); time.sleep(0.5)—— 整个异步流退化成串行 - 第三方同步库封装层:用
requests.get()时顺手加time.sleep()控频,结果拖垮服务吞吐 - 测试脚本里:REPL 中写
await asyncio.sleep(1)报错,转头换成time.sleep(1)应急,却忘了环境根本没事件循环 -
async for循环体内:逐行读aiofiles时插time.sleep(0.1),异步读取变成单行阻塞
真没法异步时,别硬套 asyncio.sleep()
遇到必须调用的同步函数(如老 SDK、本地计算、subprocess.run()),正确解法是剥离到线程池,而不是用 time.sleep() 掩盖问题:
- IO 类阻塞:用
loop.run_in_executor(None, blocking_func, *args) - 需控并发数:传
ThreadPoolExecutor(max_workers=3),避免默认池挤占事件循环资源 - CPU 密集型:换
ProcessPoolExecutor,否则线程池反而加剧 GIL 竞争 - 绝对不要在
run_in_executor里传 lambda 或闭包引用大对象,容易内存泄漏
真正麻烦的从来不是“不知道该用哪个 sleep”,而是把 time.sleep() 当作通用延时工具,嵌在异步上下文里还浑然不觉。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











