asyncio程序不能直接用cProfile.run(),因其不理解async/await,仅记录事件循环前后同步调用;应改用py-spy等异步感知工具或手动在loop中启停cProfile分析同步子任务。

asyncio 程序不能直接用 cProfile.run()
cProfile 本身不理解 async/await,它只会记录事件循环启动前后的同步调用栈。如果你写 cProfile.run("asyncio.run(main())"),得到的 profile 几乎全是 asyncio.run 和底层 C 函数(比如 PyEval_EvalFrameDefault),真正的协程函数名基本不会出现——因为它们没被当作普通函数调用,而是被事件循环调度执行的。
真正能抓到协程行为的方式,是让 profiler 在事件循环内部运行,或者换用支持异步上下文的工具。
- 不要对整个
asyncio.run()做cProfile.run() - 避免在
async def函数里直接调用cProfile.Profile().enable()—— 它无法跨await持续追踪 - 若必须用 cProfile,只能包裹「实际耗时的同步子任务」,比如数据库查询、JSON 解析、正则匹配等——这些才是 cProfile 能看清的部分
用 asyncio.get_event_loop().run_until_complete() + 手动启停 Profile
虽然 cProfile 不懂协程,但它能记录所有 Python 字节码执行。只要你在事件循环启动前启用、在结束之后禁用,并确保所有协程都在同一个 loop 中跑完,就能捕获大部分关键路径上的同步开销(比如 await 后面的同步回调、loop.run_in_executor 中的阻塞操作)。
示例做法:
立即学习“Python免费学习笔记(深入)”;
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
import cProfile import asyncio <p>async def main(): await asyncio.sleep(0.1)</p><h1>这里放你怀疑慢的同步逻辑</h1><pre class='brush:python;toolbar:false;'>return sum(i*i for i in range(10**6))
if name == "main": profiler = cProfile.Profile() profiler.enable() asyncio.get_event_loop().run_until_complete(main()) profiler.disable() profiler.dump_stats("profile.prof")
注意:asyncio.get_event_loop() 在 Python 3.7+ 默认返回当前线程的 event loop,但如果你用的是 asyncio.run(),它会新建并关闭 loop,导致 profiler 启用/禁用时机错位——所以这里必须显式用 run_until_complete。
更靠谱的选择:使用 asyncpg 或 aiomonitor?不,优先用 py-spy
py-spy 是专为异步 Python 设计的采样式 profiler,无需修改代码、不干扰事件循环,能准确显示每个协程的 CPU 时间分布和阻塞点。它比 cProfile 更适合定位「为什么某个协程迟迟不 yield」或「哪个 await 在等 I/O」。
- 安装:
pip install py-spy - 对正在运行的进程采样:
py-spy top --pid 12345 - 生成火焰图:
py-spy record -o profile.svg --pid 12345 - 它能识别
async def函数名、显示 await 链、标注sleep/read/write等系统调用等待状态
相比之下,cProfile 对 async/await 的“函数调用”视而不见,而 py-spy 把协程当第一公民看待——这才是分析异步瓶颈该有的视角。
别忽略 asyncio 的内置诊断开关
有时候瓶颈根本不在你的代码里,而在事件循环配置或资源竞争上。开启 asyncio 自带的调试模式,能暴露很多隐性问题:
- 设置环境变量:
PYTHONASYNCIODEBUG=1,会让 loop 检查回调延迟、打印警告(如「callback took too long」) - 在代码中启用:
asyncio.get_event_loop().set_debug(True) - 检查是否频繁创建 task:
asyncio.create_task()太多会拖慢调度;用asyncio.gather()批量提交更高效 - 确认没有意外阻塞:比如在协程里调用了
time.sleep()、requests.get()或未加await的async函数调用——这些都会让整个 loop 卡住
真正卡住异步程序的,往往不是某段计算密集型代码,而是某个没 await 的调用、一个没设 timeout 的 HTTP 请求、或者 executor 线程池满载——这些,cProfile 看不到,但日志和 py-spy 一眼就出来。


















