Отмена задачи отменяет будущее, которого ждала задача. Как это работает? - PullRequest
0 голосов
/ 13 ноября 2018

У меня есть ожидаемый объект, реализующий транзакцию запрос / ответ. Если время транзакции истекло, она будет повторена несколько раз перед тем, как отказаться и вызвать исключение.

Теперь предположим, что время всегда истекло, потому что у меня проблема с этим.

Когда задача запускает эту операцию, а затем отменяется, повторные попытки продолжаются. Это не то, что я хочу. Я хочу полностью отменить операцию.

Я подготовил 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 голосов
/ 13 ноября 2018

Я подготовил MCVE и заметил, что будущее, которое ожидает задача, отменяется, когда задача отменяется.Это меня вполне устраивает, это может послужить основой для решения, но я не понимаю, почему это будущее отменяется, и могу ли я положиться на него.

Да, если задача ожидает будущего,это будущее будет отменено.Это будущее может быть еще одной задачей, поэтому отмена будет распространяться на самое нижнее будущее, которое ожидается.Реализация гарантирует этого, но документация не делает этого явным.

Я бы далее полагался на это поведение по двум причинам:

  • невозможно изменить его на этом этапе без серьезного нарушения обратной совместимости.Разработчики уже отклонили меньшие изменения, потому что они сломали бы существующий код.

  • не существует другого способа реализовать это, которое не приведет кутечкам ресурсов.Если задача, которую вы отменяете, ожидает будущего, что вы делаете, кроме как отменить ее?Если вы просто дадите ему работать в фоновом режиме, вы потенциально будете держать его всегда, потому что будущее никогда не исчезнет само по себе.Если бы это было «исправлено», просто удалив его из планировщика (опять же, без отмены), у будущего никогда не было бы возможности очистить приобретенные ресурсы, что, несомненно, привело бы к утечке ресурсов.

Таким образом, можно с уверенностью рассчитывать на отмену распространения вниз, за ​​исключением фьючерсов, экранированных с asyncio.shield(), которые зарезервированы для фьючерсов, которые означают , чтобы работать в фоновом режиме и иметьих собственное управление жизнью.

...