取消任务将取消任务在等待的将来。如何运作?

时间:2018-11-13 08:51:35

标签: python python-asyncio

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

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

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

我准备了一个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+

输出(省略回溯):

REQUEST
TIMEOUT
future 'PENDING'
REQUEST
--CANCELLED--
TIMEOUT
future 'CANCELLED'   <-- why?
REQUEST
TIMEOUT
future 'CANCELLED'
Exception in callback ClientRPC.handle_timeout()
handle: 
asyncio.base_futures.InvalidStateError: invalid state

1 个答案:

答案 0 :(得分:2)

  

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

是的,如果任务等待一个未来,那么该未来将被取消。那个未来可能是另一项任务,因此取消将扩展到等待的最底层的未来。的实现makes sure,但文档并未明确说明。

出于两个原因,我将继续依赖此行为:

  • 如果不对后向兼容性造成重大破坏,就无法在此时进行更改。开发人员已经拒绝了smaller更改,因为它们会破坏现有代码。

  • 没有其他其他方法可以实现这一目标,而不会导致资源泄漏。如果要取消的任务正在等待将来,除了取消该任务外,您该怎么办?如果只是让它在后台运行,则可能会使它永远存在,因为未来可能永远不会独立存在。如果只是通过将其从调度程序中删除(同样又没有取消)来“修复”,那么未来将永远没有机会清理它获取的资源,这肯定会导致资源泄漏。

因此,可以安全地依靠取消向下传播,但用asyncio.shield()保护的期货除外,该期货保留给 meant 以便在后台运行的期货,有自己的终身管理。