避免同步递归压栈,应改用while循环+状态变量;显式await递归调用并加深度保护;广度优先场景用队列与协程池替代;切忌漏写await、滥用setrecursionlimit或嵌套run()。

核心是避免同步递归压栈,把控制权交还事件循环。异步函数本身不递归调用栈,但若在 await 前直接调用自身(比如忘记 await 或在 try 块里无等待地重试),就会触发 Python 默认的递归深度限制,报 RecursionError。
改用 while 循环 + 状态变量
这是最稳妥、最易理解的方式。每次迭代都是一次独立调度单元,不会累积栈帧。
- 用
retries或depth作为计数器,放在循环条件中 - 每次失败或需要继续时,更新状态并
continue,不调用自身 - 关键操作后加
await asyncio.sleep(0)或真实 IO(如await client.get(...)),确保让出控制权
显式 await 递归调用,但加深度保护
仅适用于深度可控的场景(如树遍历最多 5 层、爬虫最多 3 级跳转)。
- 必须对每个递归调用加
await,不能漏写 - 传入
depth参数,并在入口处检查是否超限:if depth >= max_depth: return - 避免在
asyncio.run()内启动高深度递归——它每次都会新建事件循环,开销大且无法跨循环 await
用队列或协程池替代递归结构
适合广度优先、分支多、深度不确定的场景(如目录遍历、链接爬取)。
- 初始化一个
asyncio.Queue,把初始任务(如根 URL 或路径)放入 - 启动固定数量的工作协程(如
asyncio.create_task(worker())),每个从队列取任务、处理、把子任务再推入队列 - 配合
asyncio.Semaphore控制并发数,防止单次发起过多请求压垮服务端或连接池
避免常见陷阱
这些写法看着像异步,实则危险:
-
func(n-1)而不是await func(n-1):返回未执行的协程对象,逻辑中断且无提示 -
return func(url, retries-1)在except块里:同步递归链,栈深增长 - 滥用
sys.setrecursionlimit():扩大栈只是掩耳盗铃,不解决调度阻塞和内存堆积问题 - 在非 async 函数里嵌套调用
asyncio.run()或run_until_complete():引发运行时异常,且无法共享事件循环

















