Всегда ли полезно отменить будущее asyncio? - PullRequest
0 голосов
/ 16 ноября 2018

Я пытаюсь понять использование Future.cancel () с asyncio. Документация Python очень легка в этом. Я не имел успеха с существующими вопросами здесь или поисковыми системами. Я просто хочу понять, что происходит, когда задача ожидает будущего, которое отменено.

Вот мой код:

import asyncio 

async def foo(future):
    await asyncio.sleep(3)
    future.cancel()

async def bar(future):
    await future
    print("hi")

async def baz(future):
    await bar(future)
    print("ho")

loop = asyncio.get_event_loop()
future = loop.create_future()
loop.create_task(baz(future))
loop.create_task(foo(future))
loop.run_forever()

"hi" не печатается. Поэтому я изначально догадывался, что bar возвращался на линию await future в случае отмены.

Однако "ho" также не печатается. Таким образом, кажется логичным, что отмена будущего никогда не вернется к задачам, ожидающим его? Но тогда эти задачи навсегда останутся в цикле событий? Это кажется нежелательным, где я неправильно понял?

1 Ответ

0 голосов
/ 16 ноября 2018

В этом случае ответ лежит в документации, но вы должны немного поискать его.Во-первых, напоминание о том, что означает ожидание будущего:

# the expression:
x = await future

# is equivalent to:
... magically suspend the coroutine until the future.done() becomes true ...
x = future.result()

Другими словами, после возобновления выполнения сопрограммы, содержащей await, значение *Заявление 1007 * будет result() ожидаемого будущего.

Вопрос: когда вы отменяете будущее, каков его результат? документация гласит:

Если будущее было отменено , этот метод вызывает исключение CancelledError.

Поэтому, когда кто-то отменяет ожидаемое вами будущее, выражение await future вызовет исключение!Это четко объясняет, почему bar не печатает hi (потому что await future поднял), и почему baz не печатает ho (потому что await bar(...) поднялось).

Обратная трассировка никогда не печатается, потому что loop.create_task порождает сопрограмму в «фоне» (своего рода) - если никто не проверяет возвращаемое значение, исключение будет потеряно.А поскольку вы выбросили объект task , возвращаемый create_task, и использовали run_forever, чтобы цикл работал вечно, цикл просто продолжает работать, ожидая (навсегда) новых задач, которые каким-то образом поступят.

Если вы изменили код для фактического сбора результата bar, вы легко могли бы наблюдать вывод CancelledError:

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    future = loop.create_future()
    loop.create_task(foo(future))
    loop.run_until_complete(baz(future))

:

Traceback (most recent call last):
  File "xxx.py", line 19, in <module>
    loop.run_until_complete(baz(future))
  File "/usr/lib/python3.5/asyncio/base_events.py", line 387, in run_until_complete
    return future.result()
  File "/usr/lib/python3.5/asyncio/futures.py", line 266, in result
    raise CancelledError
concurrent.futures._base.CancelledError
...