这是基本代码(不好意思太长了)
import argparse
import asyncio
from contextvars import ContextVar
import sys
# This thing is the offender
message_var = ContextVar("message")
class ServerProtocol(asyncio.Protocol):
def connection_made(self, transport):
peername = transport.get_extra_info("peername")
print("Server: Connection from {}".format(peername))
self.transport = transport
def data_received(self, data):
message = data.decode()
print("Server: Data received: {!r}".format(message))
print("Server: Send: {!r}".format(message))
self.transport.write(data)
print("Server: Close the client socket")
self.transport.close()
class ClientProtocol(asyncio.Protocol):
def __init__(self, on_conn_lost):
self.on_conn_lost = on_conn_lost
self.transport = None
self.is_connected: bool = False
def connection_made(self, transport):
self.transport = transport
self.is_connected = True
def data_received(self, data):
# reading back supposed contextvar
message = message_var.get()
print(f"{message} : {data.decode()}")
def connection_lost(self, exc):
print("The server closed the connection")
self.is_connected = False
self.on_conn_lost.set_result(True)
def send(self, message: str):
# Setting context var
message_var.set(message)
if self.transport:
self.transport.write(message.encode())
def close(self):
self.transport.close()
self.is_connected = False
if not self.on_conn_lost.done():
self.on_conn_lost.set_result(True)
async def get_input(client: ClientProtocol):
loop = asyncio.get_running_loop()
while client.is_connected:
message = await loop.run_in_executor(None, input, ">>>")
if message == "q":
client.close()
return
client.send(message)
async def main(args):
host = "127.0.0.1"
port = 5001
loop = asyncio.get_running_loop()
if args.server:
server = await loop.create_server(lambda: ServerProtocol(), host, port)
async with server:
await server.serve_forever()
return
on_conn_lost = loop.create_future()
client = ClientProtocol(on_conn_lost)
await loop.create_connection(lambda: client, host, port)
await get_input(client)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"--server", "-s", default=False, action="store_true", help="Start server"
)
arguments = parser.parse_args(sys.argv[1:])
asyncio.run(main(args=arguments))
这会崩溃,并产生以下异常:
Exception in callback _ProactorReadPipeTransport._loop_reading(<_OverlappedF...shed result=4>)
handle: <Handle _ProactorReadPipeTransport._loop_reading(<_OverlappedF...shed result=4>)>
Traceback (most recent call last):
File "C:UsersbrentAppDataLocalProgramsPythonPython310libasyncioevents.py", line 80, in _run
self._context.run(self._callback, *self._args)
File "C:UsersbrentAppDataLocalProgramsPythonPython310libasyncioproactor_events.py", line 320, in _loop_reading
self._data_received(data, length)
File "C:UsersbrentAppDataLocalProgramsPythonPython310libasyncioproactor_events.py", line 270, in _data_received
self._protocol.data_received(data)
File "E:DevelopmentPythonibcs2023_prepexperimentalasyncio_context.py", line 40, in data_received
message = message_var.get()
LookupError: <ContextVar name='message' at 0x0000023F30A54FE0>
The server closed the connection
为什么调用message = message_var.get()
会导致崩溃?为什么Python找不到上下文变量?为什么data_received
和send
不在同一个上下文中?我怎样才能使它们保持在相同的上下文中?
我正在一个更大的项目中使用text的主分支,它使用一个上下文变量,每次使用上述代码的修改版本接收消息时都会丢失上下文。
保持一个独立的"上下文";因为每个任务都是关于上下文变量的。如果您能够控制"上",则只能断言在同一上下文中调用send
和data_received
方法。(与"底层"相反)协议类的驱动程序-事实并非如此,两者都在不同的上下文中被调用。我的意思是,这个问题的答案是,我怎样才能把它们放在同一个环境中?是:你不能,除非你在asyncio内部编写自己的代码实现。
您无法跟踪消息中的元数据,并在获得回复时检索此元数据,除非消息本身上有一个标记,该标记将在往返过程中幸存下来。也就是说:您的网络/通信协议本身必须指定一种识别消息的方法,例如,它可能像在每个字符串前面加上一个连续整数一样简单,或者,在这种情况下,您只是回传消息,它可能是消息本身。一旦你有了这些,一个简单的字典有这些消息id作为键,将工作为您在这个例子中似乎想要的。