一、问题现象描述
在使用asyncio.get_event_loop().run_forever()方法时,开发者经常遇到事件循环意外阻塞或卡死的情况。典型表现包括:
- 程序无响应且CPU占用率异常升高
- 键盘中断(
Ctrl+C)无法终止进程 - 日志输出突然停止但进程仍在运行
二、根本原因分析
2.1 协程泄漏(Coroutine Leak)
最常见的卡死原因是未完成的协程堆积。当使用create_task()创建任务但未妥善处理时:
async def faulty_task():
while True: # 无限循环且无await
pass
loop = asyncio.get_event_loop()
loop.create_task(faulty_task()) # 泄漏的协程
loop.run_forever()
这类CPU密集型协程会独占事件循环,因为缺少await语句导致无法切换上下文。
2.2 同步阻塞调用
在协程中混用同步I/O操作会直接阻塞事件循环:
async def blocking_call():
time.sleep(10) # 同步睡眠
requests.get(url) # 同步HTTP请求
这类调用会导致整个事件循环停止响应,违反异步编程的基本原则。
2.3 异常处理缺失
未捕获的异常会使任务静默失败:
async def crash_task():
raise RuntimeError("unhandled")
loop.create_task(crash_task()) # 异常未被捕获
loop.run_forever()
虽然任务已终止,但事件循环仍会持续运行。
三、解决方案
3.1 使用健康检查机制
实现看门狗定时器监控事件循环状态:
async def watchdog():
while True:
await asyncio.sleep(5)
if not loop.is_running():
loop.stop()
3.2 正确管理任务生命周期
通过Task对象跟踪所有任务:
tasks = set()
async def safe_task():
try:
# 业务逻辑
finally:
tasks.discard(asyncio.current_task())
task = loop.create_task(safe_task())
tasks.add(task)
3.3 替换危险API调用
将同步操作替换为异步等效实现:
| 同步API | 异步替代方案 |
|---|---|
| time.sleep() | asyncio.sleep() |
| requests.get() | aiohttp.ClientSession.get() |
| open() | aiofiles.open() |
四、高级调试技巧
4.1 使用asyncio调试模式
启用慢回调检测:
loop.set_debug(True)
loop.slow_callback_duration = 0.1 # 100ms阈值
4.2 分析事件循环状态
通过asyncio.all_tasks()检查存活任务:
def dump_tasks():
for t in asyncio.all_tasks(loop):
print(f"{t.get_name()} {t.done()}")