能,contextvars 在同一线程、同 event loop 的协程链中可跨 await 传递,本质是每次 await 自动复制上下文快照;但 create_task() 默认不继承上下文,需显式使用 contextvars.copy_context()。

contextvars 在 asyncio 协程里真能跨 await 传递吗
能,但只在同一线程、同一 event loop 的协程链中生效。它不是全局变量,也不是线程局部存储的简单替代,而是协程局部的“上下文快照”——每次 await 后,Python 会自动把当前 ContextVar 的值复制到新协程的上下文中。
常见错误现象:ContextVar.get() 报 LookupError: No value for context variable,通常是因为:在未 set 的协程里直接 get;或误以为子线程/新 loop 能继承值(实际不能);或用了 loop.run_in_executor 后没手动拷贝上下文。
- 必须在协程内调用
var.set(value),不能在普通函数或线程里 set - 使用
asyncio.to_thread()或run_in_executor时,需显式传入contextvars.copy_context()并在目标函数中 restore - 不推荐在
__aenter__/__aexit__中依赖 contextvars 自动传播——某些装饰器或中间件可能打断上下文链
如何安全地给 HTTP 请求注入 request_id
这是 contextvars 最典型的落地场景:避免在每层函数签名里硬塞 request_id 参数。核心是「入口处 set,全程隐式携带」。
使用场景:FastAPI/Starlette 中间件、aiohttp 的 on_request_start 钩子、或自定义异步日志处理器。
立即学习“Python免费学习笔记(深入)”;
- 定义变量要顶层声明:
request_id_var = contextvars.ContextVar("request_id", default=None) - 中间件中 set 前先生成唯一值,例如:
request_id_var.set(str(uuid4())) - 日志格式化时直接取:
request_id_var.get()—— 不用传参,也不用从 scope 或 headers 里反复捞 - 注意:若用 uvicorn 的
workers > 1,每个 worker 是独立进程,contextvars 不跨进程,此时仍需靠 headers 或 trace-id 透传
为什么 asyncio.create_task() 后 contextvars 有时失效
因为 create_task() 默认不会继承调用者的上下文,尤其在 Python context=None 就会丢。
性能影响:开启上下文继承有微小开销(复制 dict),但远小于手动传参或全局 dict 查找;兼容性上,3.7+ 都支持 contextvars,但上下文继承行为差异必须明确。
- 安全写法(兼容所有版本):
asyncio.create_task(coro, context=contextvars.copy_context()) - 不要依赖
asyncio.ensure_future(),它不接受context参数,且语义更模糊 - 如果用第三方库封装了 task 创建(如
anyio.spawn),查清它是否透传 contextvars,否则可能静默丢失
和 threading.local 对比:什么时候该换用 contextvars
threading.local 只解决多线程,对 async 完全无效——协程切换不触发线程切换,local 里的值在 await 后就“消失”了。contextvars 是 asyncio 原生协同设计的,不是模拟。
容易踩的坑:有人把 threading.local 和 contextvars 混用,比如在 sync 函数里 set local,在 async 函数里 get contextvar,这必然失败。
- 纯同步代码(无 await):用
threading.local更轻量 - 混合场景(比如 sync 函数被 async 调用):必须统一用
contextvars,并在 sync 入口处确保已 set - Web 框架中,别在 WSGI 应用里用 contextvars——WSGI 是同步模型,上下文无法跨请求延续
复杂点在于:上下文不是“绑定到协程对象”,而是绑定到“协程执行时的控制流”。一旦你用 sys.settrace、C 扩展或某些调试工具干扰了协程调度,contextvars 就可能意外断裂——这种问题难复现,也难调试。


















