生成器内部不能“自愈”网络瘫痪,throw() 是异常注入点而非重试引擎,仅在暂停处抛异常并触发清理;真正弹性需外部重试循环+throw协同清理资源。

直接说结论:生成器内部不能“自愈”网络瘫痪,throw() 不是重试控制器,而是流程中断开关。它无法让 yield 自动重试、等待或切换策略。但你可以用 throw 配合外部状态机 + 指数退避逻辑,在生成器被中断后触发可控恢复——这属于“协同式弹性”,而非“yield 自愈”。
throw 的真实角色:异常注入点,不是重试引擎
throw() 的作用是在生成器当前暂停的 yield 处立即抛出指定异常(如 ConnectionError),它不唤醒、不等待、不延时,只做一件事:强制跳转到最近的 try-except 或终止生成器。它本身不包含时间控制、计数、退避逻辑。
所以,指望在 yield 表达式里写个 throw(ConnectionError) 就自动指数退避,是误解了它的定位。
真正可行的组合结构:生成器 + 外部重试循环 + throw 协同清理
把生成器当作一个“任务单元”,由外部调度器管理其生命周期。当网络失败时,外部捕获异常 → 触发指数退避 → 用 throw 清理当前生成器残留状态(如关闭 socket、释放锁)→ 启动新实例。
示例关键结构:
- 生成器内部专注单次请求:yield 仅返回一次响应,或在失败时主动 raise ConnectionError
- 外部 while 循环维护退避参数(base_delay、max_retries、attempt)
- 每次重试前 new_gen = api_call_generator(),避免复用已终止生成器
- 若生成器中途崩溃(如 socket.timeout),外部用 gen.throw(GeneratorExit) 确保 finally 块执行资源清理
为什么必须分离“退避”和“throw”
生成器函数一旦定义就固定了执行路径。你在 yield 后加 time.sleep(2 ** attempt) 是阻塞的,会卡死整个协程调度;而 throw 必须在暂停点调用,不能自己“等几秒再 throw”。二者职责天然分离:
- 退避逻辑属于调用方决策层(控制何时重试、等多久)
- throw 属于清理层(告诉生成器:“你这次失败了,现在收摊”)
- yield 本身只是暂停锚点,不参与策略计算
一个轻量但完整的协作示意
不是把退避塞进生成器,而是让生成器暴露“可中断接口”,外部按需 throw 并重建:
def http_fetcher(url):
conn = None
try:
conn = create_socket(url)
yield "connecting..."
data = conn.recv(4096)
yield {"status": "success", "data": data}
except ConnectionError as e:
yield {"status": "error", "reason": str(e)}
raise # 让外部知道该重试了
finally:
if conn:
conn.close()
<h1>外部弹性调度器</h1><p>def resilient_fetch(url, max_retries=5):
for i in range(max_retries):
gen = http_fetcher(url)
try:
next(gen) # 启动
result = gen.send(None) # 获取结果
if result["status"] == "success":
return result
except (ConnectionError, StopIteration):
if i </p><p>这里 throw(GeneratorExit) 不是驱动重试,而是确保每次失败后 socket 真的关了——这才是它对弹性的实际贡献。</p>











