我最近使用python-socketio和aiohttp编写了一个客户端/服务器应用程序,我的应用程序基于异步名称空间(服务器端),此外,我的on_message事件中有许多等待调用,因此必须使用异步锁以确保我保持所需的流量。为了实现此行为,我编写了一个装饰器,并用它包装了每个关键部分类型的函数。
@async_synchronized('_async_mutex')
async def on_connect(self, sid, environ):
self._logger.info("client with sid: {} connected to namespace: {}!".format(
sid, __class__.__name__))
important_member = 1
await other_class.cool_coroutine()
important_member = 2
在我的构造函数中,我已经初始化_async_mutex = asyncio.Lock()
装饰器:
def async_synchronized(tlockname):
"""A decorator to place an instance based lock around a method """
def _synched(func):
@wraps(func)
async def _synchronizer(self, *args, **kwargs):
tlock = self.__getattribute__(tlockname)
try:
async with tlock:
return await func(self, *args, **kwargs)
finally:
pass
return _synchronizer
return _synched
现在,在任何正常使用的情况下,一切都可以正常工作(关闭/打开客户端会正确触发功能,并且锁将按预期执行)。 请务必注意,我的on_disconnect函数使用完全相同的装饰器和锁包装。 我遇到的问题是,当客户端的网络适配器物理断开连接时(正常的客户端关闭工作正常),我看到确实调用了on_disconnect事件,但另一个协同例程当前持有该锁。由于某种原因,该事件被多次触发,并最终陷入僵局。
我已经用装饰物包装了我的装饰器,这些印刷品描述了锁的状态/调用功能,并且还在每个异步调用周围添加了try / catch。 似乎我所有的例程都捕获到一个已取消的异常(由aiohttp假定),因此有一种方法“取消”了该锁定,并且该锁定从未被释放。我尝试用asyncio.shield()包装每个异步调用,但是行为没有改变。
我应该在这里采用另一种异步锁方法吗? (完全删除锁可以解决问题,但可能导致应用程序的计算部分出现不确定的行为)
更多代码示例: 实际的on_connect和on_disconnect事件:
@async_synchronized('_async_mutex')
async def on_connect(self, sid, environ):
self._logger.info("very good log message")
self._connected_clients_count += 1
@async_synchronized('_async_mutex')
async def on_disconnect(self, sid):
self._logger.info("very good disconnect message")
self._connected_clients_count -= 1
await self._another_namespace_class.inform_client_disconnect(sid) # this method is wrapped with the same decorator but with a different lock
注意:另一个没有连接相同的客户端。另外,当发生网络断开时,我也看不到日志消息(我将日志级别设置为调试)