Python asyncio.sleep耗时过长问题排查:为何出现阻塞?
排查asyncio.sleep()引发的I/O Selector阻塞问题
我运行着一个对时间敏感的asyncio应用,正尝试排查并清理所有阻塞代码。使用带debug=True参数的asyncio.run()后,发现从sleep唤醒时I/O selector阻塞了0.852秒,日志如下:
20/04/23 12:04:20 WARNING asyncio: Executing <Task pending name='Task-99' coro=<CodeRunner.run_forever() running at /code/testcode.py:929> wait_for=<Future pending cb=[Task.task_wakeup()] base_events.py:1917 created at /Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/asyncio/base_events.py:427> cb=[_run_until_complete_cb() at /Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/asyncio/base_events.py:180] created at /Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/asyncio/runners.py:100> took 0.852 seconds
相关代码片段:
while True: if counter % CONTACT_SCHEDULE == 0: await self.run_once() counter = 0 log.info(f"Sleeping {CONTACT_SCHEDULE} seconds...") progress.refresh() counter += 1 await asyncio.sleep(1) # 这是第929行
我的问题:为何asyncio.sleep(1)会导致代码挂起?是否是因为其他代码同时运行造成了阻塞?
问题原因与排查方向
asyncio.sleep(1)本身是非阻塞协程,它会主动让出事件循环,不会直接导致阻塞。日志里的0.852秒阻塞,是事件循环的I/O selector在等待事件时被延迟,根源是其他同步阻塞代码占用了事件循环线程。- 事件循环是单线程运行的,如果任何任务执行了同步阻塞操作(比如
time.sleep()、同步网络请求、CPU密集计算、未异步化的I/O操作),都会卡住整个事件循环,导致asyncio.sleep的唤醒信号无法被及时处理。 - 重点排查两个点:
self.run_once():检查内部是否有未使用await的阻塞调用,比如用了requests而非aiohttp,或者time.sleep()而非asyncio.sleep(),这类同步代码会直接占用事件循环线程。progress.refresh():如果这个操作是同步且耗时的,也会阻塞事件循环。比如刷新终端进度条时涉及大量同步IO,或者有计算逻辑。
解决建议
- 把同步阻塞代码异步化:比如用
aiohttp替代requests,用asyncio.sleep()替代time.sleep()。 - 对CPU密集或同步IO操作,使用
asyncio.to_thread()或者线程池执行,避免占用事件循环线程:# 示例:将progress.refresh()放到线程中执行 await asyncio.to_thread(progress.refresh) - 调整debug参数定位阻塞点:设置
asyncio.get_event_loop().slow_callback_duration = 0.1,这样任何执行时间超过0.1秒的回调都会被日志记录,快速定位阻塞源。
内容的提问来源于stack exchange,提问作者David White
相关产品推荐
相关产品推荐

