async def函数仅创建协程对象,不会自动执行;必须用asyncio.run()或在事件循环中await调用,且需避免time.sleep()、同步IO等阻塞操作。

async def 函数不等于自动异步
定义一个 async def 函数只是创建了一个协程对象,它不会自动执行,更不会自动进入事件循环。初学者常误以为只要加了 async 就“变异步了”,结果直接调用函数(比如 my_coro())只得到一个 coroutine 对象,什么也不发生;或者在同步函数里写 await my_coro(),却没在事件循环中运行——Python 会直接报 RuntimeError: no running event loop。
常见错误场景:
- 在普通函数(如
__init__、click回调、threading.Thread.run)里直接await - 调用
async def函数但忘记await或没传给asyncio.run() - 用
if __name__ == '__main__': main()启动,而main是async def——这根本不会触发协程执行
time.sleep() 是最隐蔽的阻塞源
time.sleep() 看似 harmless,但它会彻底冻结整个事件循环,让所有协程停摆。这是初学者写出“假异步”的头号原因。它和 await asyncio.sleep() 表面相似,行为天差地别:前者是同步阻塞,后者是非阻塞挂起。
容易忽略的点:
立即学习“Python免费学习笔记(深入)”;
- 调试时随手加
time.sleep(1)测试逻辑,结果整个并发流程卡死 - 第三方库文档没细看,误用同步版 SDK(比如某云 SDK 的
.get_object()方法内部仍用requests+time.sleep()重试) - 把
time.sleep()放在async def函数里,自以为“反正我写了 async”,实则一执行就阻塞
文件读写、数据库、HTTP 请求全在偷偷阻塞
标准库的 open()、sqlite3.connect()、requests.get() 全是同步阻塞调用。它们一旦出现在 async def 函数里,哪怕只有一行,也会让当前协程和整个事件循环一起等到底。
正确替换路径:
- 文件 IO → 改用
aiofiles.open(),不是open()+.read() - HTTP 请求 → 必须用
aiohttp.ClientSession或httpx.AsyncClient,不能用requests - 数据库 → 选
aiosqlite、asyncpg、aiomysql,原生驱动如psycopg2不行 - 正则匹配大文本、JSON 解析超长字符串等 CPU 密集操作 → 用
loop.run_in_executor()托管,不能直接在协程里干
debug=True 不是可选项,是必开开关
异步代码出问题往往“不报错但不动”,靠肉眼很难定位哪一行在拖慢整个循环。启用 asyncio.run(main(), debug=True) 后,Python 会在协程执行超过 100ms 未主动让出控制权时发出警告,直指阻塞源头。
实际效果示例:
Executing <Task pending name='Task-1' coro=<io_task() running at test.py:5> ...> took 124.5 ms: blocking call to time.sleep()
这意味着你不用猜、不用加日志,就能立刻看到是哪行 time.sleep() 或哪个同步库调用在卡住事件循环。很多阻塞问题,开这个开关后 30 秒内就能定位。
真正难的不是写 async/await,而是意识到每个 I/O 操作背后都有“同步陷阱”——它不声不响,但只要混进去一行,整个异步架构就退化成单线程排队。



















