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

sha*_*ane 5 python-3.x python-asyncio

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

看来的行为cancelshield不是我所期望的那样。如果我包装了一个任务shield并取消了它,则await-ing协程将await立即从该语句返回,而不是像shield建议的那样等待任务完成。此外,与之shield一起运行的任务将继续运行,但其未来现在被取消await

文档

除了如果包含协程的协程被取消,在something()中运行的任务不会被取消。从something()的角度来看,取消没有发生。尽管其调用方仍被取消,所以“ await”表达式仍会引发CancelledError。

这些文档并不强烈暗示在被叫者完成之前可能会取消呼叫者,这是我问题的核心。

shield从取消到执行任务,然后等待其完成后再返回的正确方法是什么?

如果在-ed任务完成后再asyncio.shield()提出,这会更有意义,但是显然这里还有其他我不理解的想法。asyncio.CancelledErrorawait

这是一个简单的示例:

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()
Run Code Online (Sandbox Code Playgroud)

use*_*342 6

看来的行为cancelshield不是我所期望的那样。如果我包装了一个任务shield并取消了它,则await-ing协程将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())
Run Code Online (Sandbox Code Playgroud)

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

  • 换句话说,如果`shield()`返回的任务在取消时**没有**返回,那么它将完全破坏取消的语义。“shield()”的设计目的是拥有蛋糕(正确的取消语义)并吃掉它(包装的任务实际上没有被取消),但代价是引入了一些用法上的混乱。 (3认同)

Art*_*nov 5

如果 asyncio.shield() 在 await-ed 任务完成后引发 asyncio.CancelledError 会更有意义,但显然这里还有一些我不明白的其他想法。

asyncio.shield

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

你可以在这里看到实现

屏蔽任务免于取消然后等待它完成后再返回的正确方法是什么

你应该屏蔽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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

  • PSA:在 GitHub 中链接源代码时,按“y”将 URL 中的“.../master/...”转换为特定提交(或使用标签)。 (3认同)