async def 定义的是协程函数而非协程对象,需通过await(在协程内)或asyncio.run()触发执行;await仅支持Awaitable对象;IO密集型任务适用异步,CPU密集型反降效。

async def 定义的函数不是协程对象,而是协程函数
直接调用 async def 函数不会执行逻辑,也不会返回结果,只返回一个 coroutine 对象——它得被事件循环驱动才能跑起来。很多人卡在这一步,以为写了 async def 就自动异步了。
- 错误现象:
my_coro()返回<coroutine object my_coro at 0x...>,但控制台没输出、没报错、也没结果 - 正确做法:必须用
await(在另一个协程里)或asyncio.run()(顶层入口)来触发执行 - 不能在普通函数里写
await,会报SyntaxError: 'await' outside function;也不能在同步上下文里直接await my_coro() -
asyncio.run()是最简启动方式,但它每次调用都会新建并关闭事件循环,不适合反复调用或嵌入已有循环的场景
await 只能挂起可等待对象(Awaitable),不是所有东西都能 await
await 后面必须是 Awaitable:协程对象、Task、Future,或者实现了 __await__ 方法的对象。拿普通函数、列表、字符串甚至 None 去 await,立刻抛 TypeError: object XXX can't be used in 'await' expression。
- 常见误用:
await time.sleep(1)→ 错!time.sleep是阻塞同步函数,要换await asyncio.sleep(1) - HTTP 请求别用
requests.get(),它会卡死整个 event loop;得用aiohttp或httpx.AsyncClient - 数据库操作同理:
sqlite3.connect()不行,得选aiosqlite或asyncpg这类原生异步驱动 - 想把同步函数“变”成可 await 的?可以用
loop.run_in_executor()扔进线程池,但这是妥协方案,有开销,不是真异步
asyncio.gather() 和 asyncio.create_task() 的分工很关键
并发执行多个协程时,选错方式会导致串行、丢失异常、或任务被意外丢弃。
-
asyncio.gather(coro1(), coro2(), ...):适合「等全部完成」的场景,返回结果列表;任一协程出错,其他还在跑,但最终gather抛异常(除非加return_exceptions=True) -
asyncio.create_task(coro()):立即调度协程为后台任务,返回Task对象;适合「发出去就不管」或需要后续手动cancel()/done()检查的场景 - 别写
await asyncio.create_task(coro())—— 这等于又变回串行;应该先创建 task,再统一await它们 - 如果在循环里反复
create_task却不保存引用,task 可能被 GC 掉,导致协程静默消失(尤其在没有await或asyncio.wait等待时)
async/await 不是万能加速器,IO 密集才明显受益
纯 CPU 计算用 async def 包一层,性能反而更差——Python 的 GIL 没绕过去,还多了协程调度开销。
立即学习“Python免费学习笔记(深入)”;
- 适用场景:网络请求、文件读写(用
aiofiles)、数据库查询、WebSocket 通信等 IO 等待长的操作 - 不适用场景:数学计算、图像处理、JSON 解析(除非数据极大且用流式解析)、正则匹配等 CPU 绑定任务
- 混合场景要拆开:CPU 部分用
loop.run_in_executor()丢给线程池,IO 部分走await,别全塞进一个协程里 - 本地开发调试时,记得关掉 IDE 的「异步模式」或断点设置,某些调试器对协程支持不稳,容易跳过
await行或卡住










