我在新的Python asyncio 模块的asyncio.Protocol.data_received
回调中遇到异步问题时遇到了问题。
考虑以下服务器:
class MathServer(asyncio.Protocol):
@asyncio.coroutine
def slow_sqrt(self, x):
yield from asyncio.sleep(1)
return math.sqrt(x)
def fast_sqrt(self, x):
return math.sqrt(x)
def connection_made(self, transport):
self.transport = transport
#@asyncio.coroutine
def data_received(self, data):
print('data received: {}'.format(data.decode()))
x = json.loads(data.decode())
#res = self.fast_sqrt(x)
res = yield from self.slow_sqrt(x)
self.transport.write(json.dumps(res).encode('utf8'))
self.transport.close()
与以下客户一起使用:
class MathClient(asyncio.Protocol):
def connection_made(self, transport):
transport.write(json.dumps(2.).encode('utf8'))
def data_received(self, data):
print('data received: {}'.format(data.decode()))
def connection_lost(self, exc):
asyncio.get_event_loop().stop()
调用self.fast_sqrt
后,一切都按预期工作。
使用self.slow_sqrt
,它不起作用。
它也不适用self.fast_sqrt
上的@asyncio.coroutine
和data_received
装饰器。
我觉得我在这里缺少一些基本的东西。
完整的代码在这里:
经过测试:
两者的问题是相同的:使用slow_sqrt
,客户端/服务器只会挂起什么都不做。
答案 0 :(得分:8)
看来,这需要通过Future
解耦 - 尽管我仍然不确定这是否正确。
class MathServer(asyncio.Protocol):
@asyncio.coroutine
def slow_sqrt(self, x):
yield from asyncio.sleep(2)
return math.sqrt(x)
def fast_sqrt(self, x):
return math.sqrt(x)
def consume(self):
while True:
self.waiter = asyncio.Future()
yield from self.waiter
while len(self.receive_queue):
data = self.receive_queue.popleft()
if self.transport:
try:
res = self.process(data)
if isinstance(res, asyncio.Future) or \
inspect.isgenerator(res):
res = yield from res
except Exception as e:
print(e)
def connection_made(self, transport):
self.transport = transport
self.receive_queue = deque()
asyncio.Task(self.consume())
def data_received(self, data):
self.receive_queue.append(data)
if not self.waiter.done():
self.waiter.set_result(None)
print("data_received {} {}".format(len(data), len(self.receive_queue)))
def process(self, data):
x = json.loads(data.decode())
#res = self.fast_sqrt(x)
res = yield from self.slow_sqrt(x)
self.transport.write(json.dumps(res).encode('utf8'))
#self.transport.close()
def connection_lost(self, exc):
self.transport = None
以下是Guido van Rossum的answer:
解决方案很简单:将该逻辑写为标记的单独方法 使用
@coroutine
,然后使用data_received()
将其关闭async()
(在这种情况下为== Task()
)。这不是建造的原因 进入协议的是,如果是,它将需要替代事件 循环实现来处理协同程序。
def data_received(self, data):
asyncio.ensure_future(self.process_data(data))
@asyncio.coroutine
def process_data(self, data):
# ...stuff using yield from...
答案 1 :(得分:0)
我遇到了类似的问题,当我的MyProtocol.connection_made
被调用时,我想运行一个协程。我的解决方案非常相似,除了我的协议可以访问循环。对于使用较新版本的python的用户,以下对我有用(我使用python 3.6.8):
class MyProtocol(asyncio.Protocol):
def __init__(self, loop):
self.loop = loop
async def do_async_thing(self):
await asyncio.sleep(1)
def connection_made(self, transport):
self.transport = transport
self.loop.create_task(self.do_async_thing())
# Other member functions left out for brevity.
这很有意义-循环需要调度需要具有独立上下文的任务,即可以独立于任何其他调用堆栈运行。这就是为什么给循环一个可以运行的协程的原因,在这种情况下,do_async_thing()
以及一个类实例将在可能时调用。调用时与connection_made
成员函数无关。
值得注意的是,这也可以通过使用asyncio.ensure_future(coro, loop=None)
而不是self.loop.create_task(coro)
来实现,但是后者可能会使用默认循环。实际上,确实如此-我刚刚检查了source code。