Python Asyncio - как ждать отмененного экранированного задания? - PullRequest
0 голосов
/ 25 сентября 2018

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

Кажется, поведение cancel и shield не то, что ябудет ожидать.Если у меня есть задача, завернутая в shield, и я отменяю ее, сопрограмма await -ing немедленно возвращается из этого оператора await, а не ожидает завершения задачи, как подсказывает shield.Кроме того, задача, которая была запущена с shield, продолжает выполняться, но ее будущее теперь отменено, что не await -able.

Из документов :

за исключением того, что если сопрограмма, содержащая ее, отменяется, задание, выполняемое в чем-либо (), не отменяется.С точки зрения чего-либо () отмены не произошло.Несмотря на то, что вызывающий объект все еще отменяется, выражение «await» по-прежнему вызывает ошибку CancelledError.

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

Каков правильный метод shield задачи от отмены, а затем дождитесь ее завершения, прежде чем вернуться.

Было бы больше смысла, если asyncio.shield() поднял asyncio.CancelledError после выполнения задачи await, но, очевидно, здесь есть другая идея, которую я не понимаю.

Вот простой пример:

import asyncio

async def count(n):
  for i in range(n):
    print(i)
    await asyncio.sleep(1)

async def t():
  try:
    await asyncio.shield(count(5))
  except asyncio.CancelledError:
    print('This gets called at 3, not 5')

  return 42

async def c(ft):
  await asyncio.sleep(3)

  ft.cancel()

async def m():
  ft = asyncio.ensure_future(t())
  ct = asyncio.ensure_future(c(ft))

  r = await ft

  print(r)

loop = asyncio.get_event_loop()
loop.run_until_complete(m())

# Running loop forever continues to run shielded task
# but I'd rather not do that
#loop.run_forever()

Ответы [ 2 ]

0 голосов
/ 26 сентября 2018

Кажется, поведение cancel и shield не то, что я ожидал.Если у меня есть задача, завернутая в shield, и я отменяю ее, сопрограмма await -ing немедленно возвращается из этого оператора await, а не ожидает завершения задачи, как подсказывает shield.Кроме того, задача, которая была запущена с shield, продолжает выполняться, но ее будущее теперь отменено, что не await.

На концептуальном уровне shield походит на пулюзащитный жилет, который поглощает пулю, но впоследствии остается непригодным для использования.shield поглощает отмену и сообщает о себе как об отмене, вызывая CancelledError при запросе результата, но позволяет защищенной задаче продолжить выполнение.(Ответ Артемия объясняет реализацию.)

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

Каков правильный способ оградить задачу от отмены и затем дождаться ее завершения, прежде чем вернуть

Сохраняя два объекта: исходное задание и экранированное задание.Вы передаете экранированное задание любой функции, которая может отменить его, и вы ожидаете оригинального задания.Например:

async def coro():
    print('starting')
    await asyncio.sleep(2)
    print('done sleep')

async def cancel_it(some_task):
    await asyncio.sleep(0.5)
    some_task.cancel()
    print('cancellation effected')

async def main():
    loop = asyncio.get_event_loop()
    real_task = loop.create_task(coro())
    shield = asyncio.shield(real_task)
    # cancel the shield in the background while we're waiting
    loop.create_task(cancel_it(shield))
    await real_task

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

asyncio.get_event_loop().run_until_complete(main())

Код ожидает полного завершения задания, несмотря на отмену щита.

0 голосов
/ 26 сентября 2018

Было бы более разумно, если бы asyncio.shield () вызвал asyncio.CancelledError после того, как задача await-ed была выполнена, но, очевидно, здесь есть еще одна идея, которую я не понимаю.

asyncio.shield

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

Вы можете увидеть реализацию здесь

Как правильно защищать задачу от отмены, а затем дождаться ее завершения, прежде чем вернуть

Вы должны оградить count(5) future

async def t():
  c_ft = asyncio.ensure_future(count(5))
  try:
    await asyncio.shield(c_ft)
  except asyncio.CancelledError:
    print('This gets called at 3, not 5')
    await c_ft

  return 42

или t()будущее

async def t():
  await count(5)
  return 42    

async def m():
  ft = asyncio.ensure_future(t())
  shielded_ft = asyncio.shield(ft)
  ct = asyncio.ensure_future(c(shielded_ft))

  try:
    r = await shielded_ft
  except asyncio.CancelledError:
    print('Shield cancelled')
    r = await ft
...