协程中调用同步阻塞函数会导致事件循环卡住。因asyncio只能在await时调度任务,time.sleep()、requests.get()等会阻塞整个循环,使其他协程和任务无法执行;应改用asyncio.sleep()、aiohttp等异步替代方案,并启用debug模式捕获超时协程。

协程里调用了同步阻塞函数,事件循环就卡住了
Python 的 asyncio 事件循环只能在协程让出控制权(比如 await)时调度其他任务。一旦某个协程执行了 time.sleep()、requests.get()、json.loads()(大文件)、open().read() 等同步阻塞操作,整个事件循环就停摆——其他协程无法运行,asyncio.create_task() 提交的任务也不执行,甚至 Ctrl+C 都可能无响应。
常见误用场景:
- 在
async def函数里直接写time.sleep(2)(应改用await asyncio.sleep(2)) - 用
requests替代aiohttp或httpx.AsyncClient - 读取大文件时用
open('big.json').read()而非asyncio.to_thread(open, ...)或流式解析
用 asyncio.debug 模式快速定位卡点
启动事件循环时开启调试模式,能暴露长时间未让出控制权的协程:
import asyncio asyncio.run(main(), debug=True)
当某协程连续占用 CPU 超过 100ms(默认阈值),Python 会打印警告,类似:
Executing
这说明 fetch_data 在某处同步阻塞了半秒以上。注意:该警告只在 debug=True 且事件循环空闲检测开启时触发,生产环境默认关闭。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
还可手动调整阈值:
loop = asyncio.get_running_loop() loop.slow_callback_duration = 0.1 # 100ms 改为 100ms
识别哪些操作必须移出事件循环
不是所有耗时操作都适合留在协程中。以下类型必须隔离:
-
time.sleep()→ 改用await asyncio.sleep() - CPU 密集型计算(如
json.loads()解析 MB 级 JSON、正则匹配超长文本)→ 用await asyncio.to_thread(func, *args)(Python 3.9+)或loop.run_in_executor() - 同步 I/O(
open(),sqlite3.connect())→ 改用异步封装(aiosqlite)或线程池 - 第三方库无异步接口(如某些 SDK)→ 显式委托给线程池,避免直接调用
特别注意:asyncio.to_thread() 是轻量级封装,但频繁调用小任务(如每次只处理几十字节)反而增加调度开销,此时应批量合并或换用更高效序列化方式。
用 asyncio.current_task() + 日志辅助追踪执行流
当问题偶发、debug 模式没捕获到时,可在关键协程入口和出口加日志,并记录当前任务状态:
asyncio.all_tasks()
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










