【问题标题】:Cancelling a task cancels a future the task was waiting for. How does it work?取消任务会取消该任务正在等待的未来。它是如何工作的?
【发布时间】:2018-11-13 08:51:35
【问题描述】:

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

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

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

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

输出(省略回溯):

要求 超时 未来的“待定” 要求 - 取消 - 超时 未来“取消”

【问题讨论】:

    标签: python python-asyncio


    【解决方案1】:

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

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

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

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

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

    因此,依赖于向下传播的取消是安全的,但使用 asyncio.shield() 屏蔽的期货除外,它是为期货保留的,意味着继续在后台运行并拥有自己的自己的生命周期管理。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-10-25
      • 1970-01-01
      • 2016-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-30
      • 2014-04-09
      相关资源
      最近更新 更多