Сообщить об ошибке.

Функция shield() модуля asyncio в Python

Функция asyncio.shield() защищает awaitable объекты от отмены

Синтаксис:

import asyncio

await asyncio.shield(aw, *, loop=None)

Параметры:

  • aw - объект задачи,
  • loop=None - параметр цикла (удален в Python 3.10)

Возвращаемое значение:

  • нет.

Описание:

Функция shield() модуля asyncio защищает объект задачи от отмены методом Task.cancel().

Если аргумент aw является сопрограммой, то она автоматически назначается как задача.

Выражение:

task = asyncio.create_task(something())
res = await shield(task)

эквивалентно:

res = await something()

за исключением того, что если содержащаяся в выражении сопрограмма отменяется через res.cansel(), то задача, выполняемая в функции something() не отменяется. С точки зрения something() отмены не произошло. Хотя вызывающий ее объект отменен, выражение await по-прежнему вызовет исключение asyncio.CancelledError.

Концептуально, функция похожа на пуленепробиваемый жилет, который поглощает пулю, защищая владельца, но сам разрушается. Так же и asyncio.shield() поглощает отмену и при запросе результата сообщает о себе как об отмененной, вызывая исключение asyncio.CancelledError, но позволяет защищенной задаче продолжать выполнение.

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

Если функция something() отменяется другими способами, например изнутри себя, то такое поведение также отменит asyncio.shield().

Если необходимо полностью игнорировать отмену, что не рекомендуется, то функцию asyncio.shield() следует обернуть в try/except, как показано ниже:

task = asyncio.create_task(something())
try:
    res = await shield(task)
except CancelledError:
    res = None

Важно! Сохраняйте ссылку на задачи, переданные этой функции, чтобы задача не исчезла в процессе выполнения. Цикл событий сохраняет только слабые ссылки на задачи. Задача, на которую больше нигде нет ссылок, может быть удалена сборщиком мусора в любое время, даже до того, как она будет выполнена. Для надежных фоновых задач типа "запустил и забыл" нужно собрать их в коллекцию. Подробнее как это сделать в описании asyncio.create_task().

Начиная с Python 3.8: параметр цикла loop устарел.

Изменено в Python 3.10: аргумент цикла loop удален.

Устарело, начиная с Python 3.10: выдается предупреждение об устаревании, если aw не является объектом, подобным Future, и нет запущенного цикла событий.

Пример защиты задачи от отмены выполнения.

import asyncio

async def coro():
    print('Старт задачи...')
    await asyncio.sleep(2)
    print('закончили спать')

async def cancel_it(some_task):
    await asyncio.sleep(0.5)
    some_task.cancel()
    print('аннулирование произведено')

async def main():
    real_task = asyncio.create_task(coro())
    shield = asyncio.shield(real_task)
    # отменяем shield в фоновом режиме, пока ждем
    asyncio.create_task(cancel_it(shield))
    await real_task

    assert not real_task.cancelled()
    assert shield.cancelled()

if __name__ == '__main__':
    asyncio.run(main())

# Старт задачи...
# аннулирование произведено
# закончили спать