【发布时间】:2016-09-25 14:38:52
【问题描述】:
有时,我的协程清理代码包含一些阻塞部分(在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 bare
shield还不够。调用shield的函数仍将收到CancelledError。
标签: python python-asyncio coroutine