如果我有一个协程运行的任务不应被取消,我将把该任务包装在asyncio.shield()
中。
看来cancel
和shield
的行为不是我所期望的。如果我有一个用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()
答案 0 :(得分:5)
看来
cancel
和shield
的行为不是我所期望的。如果我有一个用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
您可以看到实现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