首页 > 解决方案 > 取消任务会取消该任务正在等待的未来。它是如何工作的?

问题描述

我有一个实现请求/回复事务的等待对象。如果事务超时,它将在放弃并引发异常之前重试几次。

现在假设它总是超时,因为这是我遇到问题的情况。

当任务开始此操作然后被取消时,重试将继续。这不是我想要的。我想完全取消操作。

我准备了一个 MCVE 并注意到,当任务被取消时,任务正在等待的未来会被取消。这很适合我,它可能是解决方案的基础,但我不明白为什么那个未来会被取消,以及我是否可以依赖它。

import asyncio

RETRIES = 2
TIMEOUT = 1.0

class ClientRPC:
    def __init__(self):
        self._reply = None
        self._retries = RETRIES

    def __await__(self):
        self.start()
        return self._reply.__await__()

    def start(self):
        loop = asyncio.get_event_loop()
        if self._reply is None:
            self._reply = loop.create_future()
        loop.call_later(TIMEOUT, self.handle_timeout)
        # send a request
        print("REQUEST")

    def handle_timeout(self):
        print("TIMEOUT")
        print("future", repr(self._reply._state))
        if self._retries > 0:
            self._retries -= 1
            self.start()
        else:
            self._reply.set_exception(RuntimeError("Timeout!"))

    def handle_reply(self, reply):
        # unused in this example
        pass

async def client():
    transaction = ClientRPC()
    try:
        reply = await transaction
    except asyncio.CancelledError:
        print("--CANCELLED--")

async def test():
    loop = asyncio.get_event_loop()
    task = loop.create_task(client())
    await asyncio.sleep(1.5)
    task.cancel()
    await asyncio.sleep(3)

asyncio.run(test()) # python 3.7+

输出(省略回溯):

要求
超时
未来的“待定”
要求
- 取消 - 
超时
未来“取消”<--为什么?
要求
超时
未来“取消”
回调 ClientRPC.handle_timeout() 中的异常
处理:
asyncio.base_futures.InvalidStateError:无效状态

标签: pythonpython-asyncio

解决方案


我准备了一个 MCVE 并注意到,当任务被取消时,任务正在等待的未来会被取消。这很适合我,它可能是解决方案的基础,但我不明白为什么那个未来会被取消,以及我是否可以依赖它。

是的,如果任务等待未来,该未来将被取消。该未来可能是另一项任务,因此取消将传播到等待的最底层未来。实现可以确保这一点,但文档没有明确说明。

我会继续依赖这种行为,原因有两个:

  • 在不严重破坏向后兼容性的情况下,此时不可能对其进行更改。开发人员已经拒绝了较小的更改,因为它们会破坏现有代码。

  • 没有其他方法可以实现这不会导致资源泄漏。如果你正在取消的任务正在等待一个未来,除了取消它你会做什么?如果您只是让它在后台运行,您可能会永远保留它,因为未来可能永远不会自行退出。如果只是通过从调度程序中删除它来“修复”它(同样,没有取消),未来将永远没有机会清理它获得的资源,这肯定会导致资源泄漏。

因此,依赖于向下传播的取消是安全的,除了用 屏蔽的期货asyncio.shield(),它保留给旨在保持在后台运行并拥有自己的生命周期管理的期货。


推荐阅读