0
有时,我的协程清理代码包含一些阻塞部分(在asyncio
的意义上说,即它们可能会产生)。asyncio:防止任务被取消两次
我尝试仔细设计它们,所以它们不会无限期地阻挡。因此,“通过契约”,协程一旦进入其清理片段就绝不能被中断。
不幸的是,我无法找到一种方法来防止这种情况发生,并且当它发生时发生坏事(无论是由实际的双重cancel
调用引起的;还是当它几乎完成本身,进行清理并恰好被取消从其他地方)。
从理论上讲,我可以委托清理一些其他的功能,具有shield
保护它,并与try
围绕着它 - except
循环,但它只是丑陋。
有没有Pythonic的方式来做到这一点?
#!/usr/bin/env python3
import asyncio
@asyncio.coroutine
def foo():
"""
This is the function in question,
with blocking cleanup fragment.
"""
try:
yield from asyncio.sleep(1)
except asyncio.CancelledError:
print("Interrupted during work")
raise
finally:
print("I need just a couple more seconds to cleanup!")
try:
# upload results to the database, whatever
yield from asyncio.sleep(1)
except asyncio.CancelledError:
print("Interrupted during cleanup :(")
else:
print("All cleaned up!")
@asyncio.coroutine
def interrupt_during_work():
# this is a good example, all cleanup
# finishes successfully
t = asyncio.async(foo())
try:
yield from asyncio.wait_for(t, 0.5)
except asyncio.TimeoutError:
pass
else:
assert False, "should've been timed out"
t.cancel()
# wait for finish
try:
yield from t
except asyncio.CancelledError:
pass
@asyncio.coroutine
def interrupt_during_cleanup():
# here, cleanup is interrupted
t = asyncio.async(foo())
try:
yield from asyncio.wait_for(t, 1.5)
except asyncio.TimeoutError:
pass
else:
assert False, "should've been timed out"
t.cancel()
# wait for finish
try:
yield from t
except asyncio.CancelledError:
pass
@asyncio.coroutine
def double_cancel():
# cleanup is interrupted here as well
t = asyncio.async(foo())
try:
yield from asyncio.wait_for(t, 0.5)
except asyncio.TimeoutError:
pass
else:
assert False, "should've been timed out"
t.cancel()
try:
yield from asyncio.wait_for(t, 0.5)
except asyncio.TimeoutError:
pass
else:
assert False, "should've been timed out"
# although double cancel is easy to avoid in
# this particular example, it might not be so obvious
# in more complex code
t.cancel()
# wait for finish
try:
yield from t
except asyncio.CancelledError:
pass
@asyncio.coroutine
def comain():
print("1. Interrupt during work")
yield from interrupt_during_work()
print("2. Interrupt during cleanup")
yield from interrupt_during_cleanup()
print("3. Double cancel")
yield from double_cancel()
def main():
loop = asyncio.get_event_loop()
task = loop.create_task(comain())
loop.run_until_complete(task)
if __name__ == "__main__":
main()
'asyncio.shield'是您的问题的推荐方式。 –
@AndrewSvetlov光秃秃的盾牌是不够的,不幸的是。调用'shield'的函数仍然会收到'CancelledError'。 – WGH