python asyncio-如何等待取消的屏蔽任务?

时间:2018-09-25 19:55:07

标签: python-3.x python-asyncio

如果我有一个协程运行的任务不应被取消,我将把该任务包装在asyncio.shield()中。

看来cancelshield的行为不是我所期望的。如果我有一个用shield包装的任务并取消了它,则await的协程立即从该await语句返回,而不是等待任务以shield的身份完成会建议。此外,使用shield运行的任务会继续运行,但是其未来将被取消,无法await取消。

来自docs

  

除非包含协程的协程被取消,否则在something()中运行的任务不会被取消。从something()的角度来看,取消没有发生。尽管其调用方仍被取消,所以“ 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 :(得分:5)

  

看来cancelshield的行为不是我所期望的。如果我有一个用shield包装的任务并取消了它,则await的协程立即从该await语句返回,而不是等待任务以shield的身份完成会建议。此外,使用shield运行的任务会继续运行,但是其未来将被取消,无法await取消。

从概念上讲,shield就像是吸收子弹的防弹背心,但此后仍然无法使用。 shield吸收取消,并报告自身为已取消,并在询问结果时引发CancelledError,但允许受保护的任务继续运行。 (Artemiy的答案解释了实现。)

取消屏蔽的方法可能有所不同,例如通过完全忽略取消,但是当前方法可确保取消“成功”,即,取消程序无法告知取消实际上是在绕过取消。这是设计使然,它使取消机制在整体上更加一致。

  

什么是阻止任务取消然后等待其完成再返回的正确方法

通过保留两个对象:原始任务和被屏蔽的任务。您将受保护的任务传递给可能最终取消它的任何功能,然后等待原始任务。例如:

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())

尽管已取消屏蔽,代码仍等待任务完全完成。

答案 1 :(得分:2)

  

在等待的任务完成后,如果asyncio.shield()引发asyncio.CancelledError会更有意义,但显然还有其他我不理解的想法。

asyncio.shield

  • 创建一个可能被取消的虚拟未来
  • 将包装好的协程作为future执行,并绑定到其上,以完成从设置好的包装好的协程中为虚拟future设置结果的回调
  • 返回虚拟未来

您可以看到实现here

  

什么是阻止任务取消然后等待其完成再返回的正确方法

您应该屏蔽count(5)的未来

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