nursery.child_tasks与nursery.parent_task父子任务关系
Trio 中父任务与子任务的关系及取消行为
在 Trio 中,任务(Task)之间的父子关系是理解结构化并发的关键。当你使用
async with trio.open_nursery() as nursery创建一个 nursery 时,会建立明确的父子任务关系:一、父任务与子任务的关系
1. 基本概念
- 父任务:创建 nursery 的任务(即执行
async with trio.open_nursery()的任务) - 子任务:通过
nursery.start_soon()启动的任务 - nursery:管理子任务的生命周期,确保所有子任务完成后才允许父任务继续
2. 关键特性
- 结构化并发:子任务的生命周期被限制在 nursery 的上下文中
- 错误传播:如果子任务抛出未处理的异常,nursery 会:
- 取消所有其他子任务
- 将异常传播给父任务
- 资源管理:nursery 确保所有子任务完成(或被取消)后才退出上下文
二、取消父任务的行为
如果父任务被取消(例如通过其取消范围),会发生以下连锁反应:
- 父任务的取消范围被触发
- 这会导致 nursery 的取消范围也被触发
- 所有子任务被取消
- 子任务会收到
Cancelled异常 - 子任务有机会执行清理代码(
try/finally块)
- 子任务会收到
- 等待所有子任务完成
- nursery 会等待所有子任务完成清理工作
- 父任务继续执行
- 一旦所有子任务完成,父任务会继续执行取消逻辑
三、代码示例
以下代码演示了取消父任务对子任务的影响:
python
运行
import trio
async def child_task(delay, name):
try:
print(f"{name} 开始执行")
await trio.sleep(delay)
print(f"{name} 正常完成")
except trio.Cancelled:
print(f"{name} 被取消,正在清理...")
await trio.sleep(0.5) # 模拟清理工作
print(f"{name} 清理完成")
raise # 必须重新抛出异常以表明任务已取消
async def parent_task():
print("父任务开始")
async with trio.open_nursery() as nursery:
print("创建子任务")
nursery.start_soon(child_task, 5, "子任务1")
nursery.start_soon(child_task, 10, "子任务2")
print("父任务即将被取消")
# 取消父任务(通常由外部触发)
nursery.cancel_scope.cancel()
print("父任务等待子任务完成清理...")
# 控制流会在这里阻塞,直到所有子任务完成或被取消
print("父任务继续执行")
trio.run(parent_task)
输出示例:
plaintext
父任务开始
创建子任务
父任务即将被取消
父任务等待子任务完成清理...
子任务1 开始执行
子任务2 开始执行
子任务1 被取消,正在清理...
子任务2 被取消,正在清理...
子任务1 清理完成
子任务2 清理完成
父任务继续执行
四、重要注意事项
-
子任务必须处理取消
- 子任务应通过
try/finally或async with确保资源正确清理 - 未处理的取消可能导致资源泄漏
- 子任务应通过
-
取消是协作式的
- Trio 的取消是协作式的,子任务需要检查取消请求
- 使用
await trio.sleep()或其他检查点函数使任务可取消
-
异常传播
- 如果子任务抛出异常,nursery 会取消所有其他子任务
- 父任务可以捕获这些异常并进行处理
-
使用
shield防止意外取消
- 如果需要保护某些代码不被取消,可以使用
shield:python运行with trio.CancelScope(shield=True): # 这里的代码不会被外部取消
- 如果需要保护某些代码不被取消,可以使用
五、总结
取消父任务会导致:
- 父任务的取消范围被触发
- 所有子任务收到取消请求
- 子任务有机会执行清理代码
- 父任务等待所有子任务完成清理
- 父任务继续执行(或传播取消异常)
这种设计确保了资源的正确清理和结构化并发,是 Trio 安全性的核心特性之一。
浙公网安备 33010602011771号