真实对同一次task.cancel()用两种方式处理CancelledError——吞掉和re-raise,验证两者最终task.cancelled()状态的真实差异。
代码async def bad_worker(): try: await asyncio.sleep(10) except asyncio.CancelledError: await asyncio.sleep(0.05) # 吞掉异常,继续跑 return "finished anyway" # 甚至还返回了正常结果 async def good_worker(): try: await asyncio.sleep(10) except asyncio.CancelledError: raise # 清理完,重新抛出 t1 = asyncio.create_task(bad_worker()) t2 = asyncio.create_task(good_worker()) await asyncio.sleep(0.1) t1.cancel(); t2.cancel()
bad_worker: 捕获到CancelledError,睡0.05秒后返回"finished anyway"
t1 (bad, swallowed cancel) result: ['finished anyway'] ← 真实拿到了一个正常结果
t1.cancelled() → False ← 尽管外部真实调用过t1.cancel(),asyncio认为它没有被取消
t2 (good, re-raised) result: [CancelledError('')]
t2.cancelled() → True ← 只有真实re-raise的这个,状态才是"已取消"这是本文认为协程取消这套机制里最真实、也最容易被写错代码的地方:task.cancel()做的事情,真实只是在这个task当前挂起等待的那个await点上,注入一次CancelledError异常——它不是一个强制杀死任务的指令,任务代码完全可以像本文bad_worker这样,捕获到这个异常之后不做任何特殊处理,正常返回一个值。asyncio判断一个task"是否真的被取消"的真实依据,不是"有没有调用过.cancel()",而是"这个task最终是不是带着CancelledError结束的"——bad_worker吞掉异常后走的是正常返回路径,t1.cancelled()真实就是False,调用方原本以为"我取消了它",但从任务自身的最终状态看,它其实是正常跑完的。
每一步都是本文上面用真实的asyncio task.cancel()跑出来的原始结果。
asyncio