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 会:
    1. 取消所有其他子任务
    2. 将异常传播给父任务
  • 资源管理:nursery 确保所有子任务完成(或被取消)后才退出上下文

二、取消父任务的行为


如果父任务被取消(例如通过其取消范围),会发生以下连锁反应:

  1. 父任务的取消范围被触发
    • 这会导致 nursery 的取消范围也被触发
  2. 所有子任务被取消
    • 子任务会收到Cancelled异常
    • 子任务有机会执行清理代码(try/finally块)
  3. 等待所有子任务完成
    • nursery 会等待所有子任务完成清理工作
  4. 父任务继续执行
    • 一旦所有子任务完成,父任务会继续执行取消逻辑

三、代码示例


以下代码演示了取消父任务对子任务的影响:

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 清理完成
父任务继续执行
 

四、重要注意事项


  1. 子任务必须处理取消

    • 子任务应通过try/finallyasync with确保资源正确清理
    • 未处理的取消可能导致资源泄漏
  2. 取消是协作式的

    • Trio 的取消是协作式的,子任务需要检查取消请求
    • 使用await trio.sleep()或其他检查点函数使任务可取消
  3. 异常传播

    • 如果子任务抛出异常,nursery 会取消所有其他子任务
    • 父任务可以捕获这些异常并进行处理
  4. 使用shield防止意外取消

    • 如果需要保护某些代码不被取消,可以使用shield
      python
       
      运行
       
       
       
       
      with trio.CancelScope(shield=True):
          # 这里的代码不会被外部取消
       

五、总结


取消父任务会导致:

  1. 父任务的取消范围被触发
  2. 所有子任务收到取消请求
  3. 子任务有机会执行清理代码
  4. 父任务等待所有子任务完成清理
  5. 父任务继续执行(或传播取消异常)

这种设计确保了资源的正确清理和结构化并发,是 Trio 安全性的核心特性之一。
 

posted on 2025-07-27 14:28  痴心妄想  阅读(9)  评论(0)    收藏  举报

导航