asyncio.future 回调导致取消后异常
asyncio.future with callback causes Exception after cancellation
在正常关闭时取消几个计划 asyncio.futures(计划为服务器的消息编写器)时,我遇到了一个尴尬的问题。
每个活动客户端连接都有一个活动编写器会话。
writer = asyncio.gather(
*[self._dispatchMessage(message, c) for c in connections],
loop=self.loop,
return_exceptions=True
)
每个作者未来 (GatheringFuture) 都存储在活动会话列表中...
connection.sessions = set()
...一旦完成,作者的未来就会从中删除。这是通过向未来添加 "done_callback" 来实现的。
writer.add_done_callback(lambda task: connection.sessions.remove(task))
connection.sessions.add(writer)
一旦我优雅地关闭了我的服务器,我就会遍历活动连接及其会话并取消 (future.cancel()) 它们,以免以未决的写入器期货结束。
async def cancel_sessions(connection):
for s in connection.sessions:
s.cancel()
asyncio.wait(
[await cancel_sessions(c) for c in client_connections],
timeout=None
)
这行得通,但是只要我有更多的连接,我就会不断收到异步循环异常,尽管贪婪地捕获异常以在几乎所有地方进行调试。对我来说,问题是由添加到 writer futures (GatheringFuture) 的 "done_callbacks" 引起的。看来我必须删除 call_backs,否则我会得到:
Traceback (most recent call last):
File "uvloop/cbhandles.pyx", line 49, in uvloop.loop.Handle._run
File "...server.py", line 353, in <lambda>
writer.add_done_callback(lambda task: connection.sessions.remove(task))
myUtilsAsyncLoopException: Async exception "<_GatheringFuture finished result=[None, None]>"
如果我从我的代码中省略这一行...
writer.add_done_callback(lambda task: connection.sessions.remove(task))
...我不明白这些问题。我现在的问题是如何在取消 asyncio.futures 时处理 done_callbacks。我的印象是,我不必在取消之前从未来手动删除此类回调。但似乎在取消带有回调的未来时,这些回调可能会在相关未来被取消时抛出异常。
我也不明白为什么只有当我有超过 2 个连接时才会发生这种情况,因为我认为处理它们没有太大区别。
我想我自己解决了。它通常有助于在问题中提出问题以更好地了解情况。我的问题似乎与 asyncio 无关,而是一个简单的逻辑问题。所有 connection.sessions 都有相同的(作者)未来,实际上 asyncio.future "done_callbacks" 仍然在取消时被调用,但是将相同的未来(作者)添加到几个 connection.sessions 导致了一个关键错误lambda 回调。
对于阅读此问题的每个人:问题源于为每个可用连接注册 "done callbacks" 到未来。这发生在 for 循环中,但回调从未绑定到 future 完成时的实际连接,而是绑定到 for 循环的最后一个连接。因此总是相同的连接对象。
文档建议使用 functools 将某些值绑定到未来的回调。这终于奏效了(将 for 循环中的值绑定到回调)并且没有 "funny" side-effects.
在正常关闭时取消几个计划 asyncio.futures(计划为服务器的消息编写器)时,我遇到了一个尴尬的问题。 每个活动客户端连接都有一个活动编写器会话。
writer = asyncio.gather(
*[self._dispatchMessage(message, c) for c in connections],
loop=self.loop,
return_exceptions=True
)
每个作者未来 (GatheringFuture) 都存储在活动会话列表中...
connection.sessions = set()
...一旦完成,作者的未来就会从中删除。这是通过向未来添加 "done_callback" 来实现的。
writer.add_done_callback(lambda task: connection.sessions.remove(task))
connection.sessions.add(writer)
一旦我优雅地关闭了我的服务器,我就会遍历活动连接及其会话并取消 (future.cancel()) 它们,以免以未决的写入器期货结束。
async def cancel_sessions(connection):
for s in connection.sessions:
s.cancel()
asyncio.wait(
[await cancel_sessions(c) for c in client_connections],
timeout=None
)
这行得通,但是只要我有更多的连接,我就会不断收到异步循环异常,尽管贪婪地捕获异常以在几乎所有地方进行调试。对我来说,问题是由添加到 writer futures (GatheringFuture) 的 "done_callbacks" 引起的。看来我必须删除 call_backs,否则我会得到:
Traceback (most recent call last):
File "uvloop/cbhandles.pyx", line 49, in uvloop.loop.Handle._run
File "...server.py", line 353, in <lambda>
writer.add_done_callback(lambda task: connection.sessions.remove(task))
myUtilsAsyncLoopException: Async exception "<_GatheringFuture finished result=[None, None]>"
如果我从我的代码中省略这一行...
writer.add_done_callback(lambda task: connection.sessions.remove(task))
...我不明白这些问题。我现在的问题是如何在取消 asyncio.futures 时处理 done_callbacks。我的印象是,我不必在取消之前从未来手动删除此类回调。但似乎在取消带有回调的未来时,这些回调可能会在相关未来被取消时抛出异常。
我也不明白为什么只有当我有超过 2 个连接时才会发生这种情况,因为我认为处理它们没有太大区别。
我想我自己解决了。它通常有助于在问题中提出问题以更好地了解情况。我的问题似乎与 asyncio 无关,而是一个简单的逻辑问题。所有 connection.sessions 都有相同的(作者)未来,实际上 asyncio.future "done_callbacks" 仍然在取消时被调用,但是将相同的未来(作者)添加到几个 connection.sessions 导致了一个关键错误lambda 回调。
对于阅读此问题的每个人:问题源于为每个可用连接注册 "done callbacks" 到未来。这发生在 for 循环中,但回调从未绑定到 future 完成时的实际连接,而是绑定到 for 循环的最后一个连接。因此总是相同的连接对象。
文档建议使用 functools 将某些值绑定到未来的回调。这终于奏效了(将 for 循环中的值绑定到回调)并且没有 "funny" side-effects.