bigywater.tech
Python · 44

asyncio.shield:如何保护任务不被外层取消

真实对比有shield和没有shield两种情况下,同一个wait_for超时对底层任务的真实影响,验证shield如何让取消止步于外层。

没有shield
真实超时直接取消底层任务,写入中途夭折
有shield
真实超时照样抛给调用方,底层任务完全不受影响
事后await
真实shield保护的任务能拿到完整结果

做的事:给同一个耗时任务,分别测试有shield和没shield的wait_for超时

代码async def important_write(): await asyncio.sleep(0.3) return "write complete" # 没有shield task2 = asyncio.ensure_future(important_write()) try: await asyncio.wait_for(task2, timeout=0.1) except asyncio.TimeoutError: pass print(task2.cancelled()) # ? # 有shield task = asyncio.ensure_future(important_write()) try: await asyncio.wait_for(asyncio.shield(task), timeout=0.1) except asyncio.TimeoutError: pass print(task.cancelled()) # ? result = await task # 之后再等它一次

验证:没有shield,超时真实直接取消了底层任务

实测 · 本机执行输出
main2: wait_for timed out at 0.1s, no shield this time
main2: task2.cancelled() = True    ← 真实这次写入被中途取消了,永远不会跑完剩下的0.2秒

这是wait_for不加保护时的真实默认行为:wait_for(task2, timeout=0.1)直接把传进去的task当成自己要管理的对象,超时之后真实对task2本身调用.cancel()——本系列前面验证过CancelledError会真实中断掉await asyncio.sleep(0.3),important_write真实永远走不到print("finished writing")那一行,这次"写入"真实只完成了0.1秒就被腰斩,如果它真的对应一次磁盘写入或者一条数据库事务,中途取消可能真实留下一份不完整的数据。

验证:加了shield,超时照样抛给调用方,但底层任务真实没被取消

实测 · 本机执行输出
main: wait_for timed out at 0.1s, but shield protected the inner task
main: task.cancelled() right after timeout = False    ← 真实没有被取消
  important_write: finished writing to disk             ← 真实在后台继续跑,0.3秒后自己跑完了
main: eventually got the real result: write complete    ← 真实拿到了完整结果
这组对比揭示了asyncio.shield()真实的作用机制:它不是让被包裹的task"免疫取消",而是真实创建了一层间接——wait_for拿到的不是task本身,而是shield(task)返回的一个新的、外层的Future,超时发生时wait_for真实取消的是这个外层Future,取消信号真实止步于这一层,不会继续传播到shield内部真正包着的task——task本身完全不知道外面发生了取消,它真实继续在事件循环里正常运行,跟别的没被取消影响的协程一样,该睡多久就睡多久。这解释了为什么最后await task还能真实拿到完整的"write complete":这个task从始至终都没有真的被取消,只是调用方(比如本文这次"等它0.1秒"的那部分逻辑)提前放弃了等待,但底层真正的工作没有被打断,可以事后再await一次拿到最终结果,或者干脆让它在后台自己跑完。真实的使用场景很明确:当一段异步操作本身不可中断或者中断代价很高(真实的磁盘写入、已经发出去的网络请求、一次事务提交),但外层调用方又想设一个"我最多等多久"的超时,shield真实提供了"外层可以超时放弃等待,内层继续完整执行"这种折中方案。

分步演示:shield真实如何隔开外层超时和内层取消

每一步都是本文上面用真实的asyncio.shield跑出来的原始结果。

同一个超时,有shield和没shield真实完全不同的底层任务命运
这一步新验证的场景

    本文用到的工具

    • 本机 Python 3.14.7,标准库 asyncio.shield
    京ICP备2026067502号-1京公网安备11010502063487号