为何Python中ContextVar在该echo服务器场景下能正常工作?
以下是一个可正常运行的echo服务器——这里的“正常”指输出显示每个用户在listen_for_messages中能获取到唯一的地址:
import asyncio from contextvars import ContextVar class Server: user_address = ContextVar("user_address") def __init__(self, host: str, port: int): self.host = host self.port = port async def start_server(self): server = await asyncio.start_server(self._on_connected, self.host, self.port) await server.serve_forever() def _on_connected(self, reader, writer): self.user_address.set(writer.get_extra_info("peername")) asyncio.create_task(self.listen_for_messages(reader)) async def listen_for_messages(self, reader): while data := await reader.readline(): print(f"Got message {data} from {self.user_address.get()}") async def main(): server = Server("127.0.0.1", 9000) await server.start_server() asyncio.run(main())
我的疑问是:为何这段代码能正常工作?
我原本的理解是每个Task拥有独立的ContextVar副本。若语句:
self.user_address.set(writer.get_extra_info("peername"))
位于listen_for_messages协程内(已显式创建新Task),或是_on_connected为协程函数(根据官方文档,协程函数会被自动调度为Task),我都不会有疑问。但该语句位于普通函数_on_connected中,此时还未创建listen_for_messages的新Task,属于main()创建的父Task,我认为会覆盖ContextVar的值,但实际却正常运行,想知道我的理解误区在哪里?
核心误区在于你对asyncio.start_server调用_on_connected的时机和上下文理解有误——_on_connected并不是在main的父Task里执行的。
当asyncio.start_server启动的服务器接收到新连接时,它会为每个新连接单独创建一个临时Task来执行_on_connected函数。也就是说,每个客户端连接触发的_on_connected都运行在独立的Task上下文里,并非共享main的那个Task。
这样一来,每个_on_connected里调用self.user_address.set()设置的ContextVar值,都是当前独立Task的专属副本。而后续通过asyncio.create_task()创建的listen_for_messages Task,会自动继承当前_on_connected所在Task的上下文——这是asyncio的Task上下文继承规则:新创建的Task会复制父Task的ContextVar状态。
所以每个listen_for_messages Task拿到的都是对应连接的地址,不会被其他连接覆盖。
总结关键点:
- 每个新连接的
_on_connected都运行在独立Task中,各自的ContextVar互不干扰 - 新创建的
listen_for_messagesTask继承了对应_on_connected的上下文,能获取正确的地址
内容的提问来源于stack exchange,提问作者S.B

