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.