asyncio.run()中误用time.sleep()会阻塞整个事件循环,正确做法是用await asyncio.sleep()或线程池执行同步阻塞操作。

asyncio.run() 里直接调用 time.sleep() 会卡死整个协程
这是最典型的阻塞误用:在 async def 函数里写 time.sleep(1),表面看只停 1 秒,实际会让所有并发任务一起等——因为 time.sleep() 是同步阻塞,它不交出控制权,asyncio 的事件循环根本没法调度其他协程。
正确做法是用 await asyncio.sleep(1),它内部是挂起当前协程、让出 CPU,不影响其他任务运行。
- 别把同步库函数(如
requests.get()、json.load()、open())直接扔进async def里 - IO 密集型同步操作(比如读文件、发 HTTP 请求)必须包装成异步,或丢给线程池
-
asyncio.sleep()是模拟延迟的唯一合规方式,不是“替代品”,是设计契约
用 concurrent.futures.ThreadPoolExecutor 跑同步阻塞任务
当必须调用无法异步化的第三方库(比如某些老 SDK、本地计算函数、subprocess.run()),就得靠线程池解耦。核心是 loop.run_in_executor(),它把同步任务提交到线程池,同时保持协程不阻塞。
示例:在异步循环中安全调用 requests.get()
立即学习“Python免费学习笔记(深入)”;
import asyncio import requests from concurrent.futures import ThreadPoolExecutor <p>async def fetch_url(url): loop = asyncio.get_running_loop()</p><h1>把 requests.get 丢进线程池执行</h1><pre class='brush:python;toolbar:false;'>return await loop.run_in_executor(None, requests.get, url)
async def main(): urls = ["https://www.php.cn/link/5f69e19efaba426d62faeab93c308f5c"] 5 tasks = [fetch_url(u) for u in urls] results = await asyncio.gather(tasks) # 并发发出 5 个请求
-
run_in_executor(None, ...)用默认线程池;显式传入ThreadPoolExecutor(max_workers=4)可控并发数 - 不要在
run_in_executor里传 lambda 或闭包引用大量外部变量,容易引发内存泄漏或状态错乱 - 线程池适合 IO 阻塞,不适合 CPU 密集型任务——那该用
ProcessPoolExecutor,否则反而拖慢事件循环
async for 循环里混用同步阻塞逻辑会隐式串行化
常见错误是在 async for(比如遍历 aiofiles 文件行、aiomysql 查询结果)中间插一段 time.sleep() 或直接调用 os.path.exists()。表面看只是“每行处理后等一下”,实际导致整个异步流退化为单线程逐行执行。
原因:阻塞调用打断了异步迭代器的 yield 流程,后续项必须等当前项彻底完成才能产出。
- 把同步耗时操作统一移到
run_in_executor中,哪怕只是os.stat() - 避免在
async for循环体内做任何未加await的 IO 或计算 - 如果必须节流(比如限速调 API),用
asyncio.sleep()+ 计数器,而不是同步 sleep
线程池没关干净会导致 asyncio 程序退出失败
常见现象:脚本跑完不退出,卡在 Event loop is closed 或直接抛 RuntimeError: Event loop is closed。根源是 ThreadPoolExecutor 实例被 GC 前没显式 shutdown(),而它的后台线程还在尝试回调到已关闭的事件循环。
解决方案只有两个:用 with 语句自动管理,或手动 shutdown(wait=True)。
- 推荐写法:
with ThreadPoolExecutor() as pool: await loop.run_in_executor(pool, func) - 若需复用线程池(比如全局实例),务必在程序退出前调用
pool.shutdown(wait=True) - 别依赖
atexit注册 shutdown——asyncio 退出顺序不可靠,容易失效
真正麻烦的从来不是“怎么加 await”,而是识别哪些调用表面无害实则暗藏阻塞。尤其当同步库封装了多层抽象(比如某个 ORM 的 .save() 方法底层调了 os.fsync()),光看函数名根本看不出它是不是阻塞的。


















