[英]Call async functions in blocking context in Tornado
我想在 Tornado 框架中实现一个基于 web sockets 的服务。 当用户关闭 web 套接字时,我想将此通知其他用户。 但是, on_close
显然是阻塞 function 和我的_broadcast(str) -> None
function 是异步的。
无论如何,我怎么称呼这个 function ?
from tornado import websocket
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class SocketHandler(websocket.WebSocketHandler):
async def open(self, *args, conns, **kwargs):
logger.info(f"Opened a new connection to client {id(self)}")
self._conns = conns
async def on_message(self, message):
logger.info(f"Client {id(self)} sent message: {message}")
await self._broadcast(message)
def on_close(self):
logger.info(f"Client {id(self)} has left the scene")
self._conns.remove(self)
self._broadcast("something") # TODO
async def _broadcast(self, msg):
for conn in self._conns:
try:
await conn.write_message(msg)
except websocket.WebSocketClosedError:
pass
app = web.Application([
(r'/ws', SocketHandler)
])
if __name__ == '__main__':
app.listen(9000)
ioloop.IOLoop.instance().start()
您正在寻找的简单解决方案是在调用协程时使用asyncio.create_task
:
def on_close(self):
logger.info(f"Client {id(self)} has left the scene")
self._conns.remove(self)
asyncio.create_task(self._broadcast("something"))
(这个 function 的传统 Tornado 版本是tornado.gen.convert_yielded
,但现在 Tornado 和 asyncio 已集成,没有理由不将 asyncio 版本用于本机协程)
但是对于这个特殊问题,在您的_broadcast
function 中使用await
并不理想。 等待write_message
用于提供流控制,但create_task
对await
提供的背压没有任何用处。 ( write_message
相当不寻常,因为它完全支持在有和没有await
的情况下调用它)。 事实上,它对错误的事情施加了背压——一个缓慢的连接会减慢向其后的所有其他连接发出的通知。
因此,在这种情况下,我建议将_broadcast
常规同步 function:
def _broadcast(self, msg):
for conn in self._conns:
try:
conn.write_message(msg)
except websocket.WebSocketClosedError:
pass
如果您希望能够更好地控制 memory 的使用(通过await write_message
提供的流控制),您将需要一个更复杂的解决方案,可能涉及每个连接的有界队列(在on_close
中,使用put_nowait
将消息添加到每个连接的队列,然后有一个任务从队列中读取并使用await write_message
写入消息)
我认为涉及使用asyncio.Queue
的解决方案应该适合您。
我做了一个小的 class 作为模型来测试这个:
import asyncio
import time
class Thing:
on_close_q = asyncio.Queue()
def __init__(self):
self.conns = range(3)
def on_close(self, id):
time.sleep(id)
print(f'closing {id}')
self.on_close_q.put_nowait((self, id))
async def notify(self, msg):
print('in notify')
for conn in range(3):
print(f'notifying {conn} {msg}')
async def monitor_on_close():
print('monitoring')
while True:
instance, id = await Thing.on_close_q.get()
await instance.notify(f'{id} is closed')
从那里,您需要在从龙卷风获得的ioloop
中运行monitor_on_close
。 我从来没有用过龙卷风,但我认为在你的__main__
块中添加这样的东西应该可以工作:
ioloop.IOLoop.current().add_callback(monitor_on_close)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.