当前位置:首页 > 文章列表 > 文章 > python教程 > Python asyncio.Runner 怎样为不同任务切换 contextvars 上下文

Python asyncio.Runner 怎样为不同任务切换 contextvars 上下文

来源:17golang原创 2026-11-05 00:15:53 0浏览 收藏

如果程序需要在同一个事件循环里连续调用多个异步入口,asyncio.Runner 很合适;但它不会替你为每次 run() 自动创建全新的 contextvars.Context。要让不同任务的请求标识、租户号或追踪变量互不串值,做法是:为每个入口复制一份上下文,写入该入口的变量,再把它传给 runner.run(coro, context=ctx)。

官方文档:https://docs.python.org/3/library/asyncio-runner.html

要点速览
  • Runner 负责复用事件循环,Context 负责隔离上下文状态,二者不是一回事。
  • copy_context() 复制当前上下文,Runner.run(context=...) 决定本次入口使用哪一份。
  • 同一份 Context 不要被并发重新进入;并发子任务应在各自上下文中创建或显式传递上下文。

一、先区分 Runner 的事件循环与 ContextVar 上下文

Runner 的价值是让多个顶层异步函数共享一个嵌入式事件循环和默认上下文。共享循环可以减少反复初始化,但默认上下文中的变量修改也可能被下一次入口看到。例如第一个入口把 request_id 设成 req-a,第二个入口没有覆盖它,就会读到上一轮留下的值。

这里的边界很容易混淆:关闭 Runner 会收尾异步生成器、关闭默认执行器并关闭事件循环;它不等于每次运行都清空业务上下文。要隔离业务状态,必须在调用入口处显式准备 Context。

Python asyncio Runner 事件循环与 contextvars 上下文分离的静态结构图
图1:说明图展示 asyncio.Runner 复用事件循环与 ContextVar 隔离业务状态的两条边界。

二、用 copy_context 为每个入口复制独立上下文

下面的写法把“准备上下文”和“运行协程”放在一起。copy_context() 先复制当前上下文,ContextVar.set() 只修改副本;随后通过 context=ctx 把副本交给 Runner。set() 返回的 token 用于在当前副本内恢复旧值,适合把临时修改包在 try/finally 中。

import asyncio
import contextvars

# 在模块级创建上下文变量,避免闭包长期持有不可见引用
request_id = contextvars.ContextVar("request_id", default="unknown")

async def handle(name: str) -> str:
    # 这里读取的是本次 Runner.run 绑定的上下文
    await asyncio.sleep(0)
    return f"{name}: {request_id.get()}"

def run_with_request(runner: asyncio.Runner, name: str, value: str) -> str:
    # 为本次入口复制上下文,避免直接改 Runner 的默认上下文
    ctx = contextvars.copy_context()
    ctx.run(request_id.set, value)
    # context 参数决定这个协程从哪一份 Context 开始运行
    return runner.run(handle(name), context=ctx)

with asyncio.Runner() as runner:
    # 两次 run 共享事件循环,但读取到的 request_id 各自独立
    print(run_with_request(runner, "first", "req-a"))
    print(run_with_request(runner, "second", "req-b"))

预期输出是两行不同的请求标识。这个结果只能说明上下文绑定正确,不能说明两个入口拥有不同事件循环;事件循环仍由同一个 Runner 管理。

Python copy_context 与 Runner.run context 参数为不同入口绑定独立请求上下文的结构图
图2:结构图展示 copy_context、ContextVar.set 和 Runner.run(context=ctx) 的绑定关系。

三、让同一入口内的子任务继承正确上下文

如果一个入口内部还要创建多个任务,先决定它们是否应该共享请求级变量。需要同一请求标识时,可以在对应 ctx 中启动入口,让子任务继承入口上下文;需要两个任务各自拥有值时,则分别复制上下文并在创建任务时传入。

async def worker(label: str) -> str:
    # 子任务从创建时绑定的上下文读取变量
    await asyncio.sleep(0)
    return f"{label}: {request_id.get()}"

async def run_workers() -> list[str]:
    # 每个副本只服务一个任务,避免同一 Context 被并发进入
    left = contextvars.copy_context()
    left.run(request_id.set, "req-left")
    right = contextvars.copy_context()
    right.run(request_id.set, "req-right")

    task_left = asyncio.create_task(worker("left"), context=left)
    task_right = asyncio.create_task(worker("right"), context=right)
    # gather 只汇总结果;上下文隔离由 create_task 的 context 参数完成
    return await asyncio.gather(task_left, task_right)

这里不要把同一个 Context 同时传给两个并发任务。Context 是可进入的上下文栈,重复并发进入会触发运行时错误;复制两份才是清晰的边界。

四、用清理和检查清单收住边界

场景建议检查点
多个入口顺序执行每个入口使用 copy_context() 和 context=...第二个入口不能读到第一个入口的请求标识
一个入口并发多个任务按任务复制 Context,创建任务时显式传入不要并发重入同一 Context
程序退出使用 with asyncio.Runner() 管理生命周期让 Runner 负责循环、异步生成器和默认执行器收尾

如果变量只是当前协程的临时数据,优先用局部变量;只有需要跨调用链隐式传递、又不想依赖线程局部变量时,才使用 ContextVar。上下文隔离解决的是状态可见范围,不是取消传播、线程安全或数据库事务问题。

相关问题

只调用一次 Runner.run,还需要传 context 吗?

不一定。若没有跨入口复用状态,默认上下文通常足够;一旦入口代表不同请求或租户,显式传递更容易审查。

copy_context 会复制 ContextVar 对象里的可变对象吗?

它复制的是上下文映射,不会深复制变量值。若值本身是列表或字典,仍要避免多个任务共同修改同一个可变对象。

Runner.close 能不能替代上下文清理?

不能。close() 负责 Runner 的异步资源和事件循环生命周期;请求标识是否隔离仍由每次运行绑定的 Context 决定。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Python selectors.DefaultSelector 怎样管理多路 I/O 事件Python selectors.DefaultSelector 怎样管理多路 I/O 事件
上一篇
Python selectors.DefaultSelector 怎样管理多路 I/O 事件
Python inspect.getclosurevars怎样查看函数依赖的外部变量
下一篇
Python inspect.getclosurevars怎样查看函数依赖的外部变量
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    887次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    883次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    968次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    952次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    780次使用