这是一个非常好的问题。在回答这个问题的过程中,我学到了一些东西,所以我希望你仍然关注这个主题。
首先要调查的是,shield() 方法是如何工作的? 在这一点上,文档非常令人困惑。 我直到读了 test_tasks.py 中标准库测试代码才弄明白它。 这是我的理解:
考虑以下代码片段:
async def coro_a():
await asyncio.sheild(task_b())
...
task_a = asyncio.create_task(coro_a())
task_a.cancel()
当执行任务 task_a.cancel() 语句时,确实取消了 task_a 。await 语句立即抛出 CancelledError 异常,而不等待 task_b 完成。但是 task_b 将继续运行。外部任务(a)停止,但内部任务(b)不会停止。
这是您程序的修改版本,用于说明此情况。主要更改是在取消错误异常处理程序中插入等待,以使您的程序保持活动状态几秒钟。我在 Windows 上运行,这就是为什么我稍微更改了信号处理程序,但这只是一个小问题。我还向打印语句添加了时间戳。
import asyncio
import signal
import time
async def task1():
print("Starting simulated task1", time.time())
await asyncio.sleep(5)
print("Finished simulated task1", time.time())
async def task2():
print("Starting simulated task2", time.time())
await asyncio.sleep(5)
print("Finished simulated task2", time.time())
async def tasks():
await task1()
await task2()
async def task_loop():
try:
while True:
await asyncio.shield(tasks())
await asyncio.sleep(60)
except asyncio.CancelledError:
print("Shutting down task loop", time.time())
raise
async def aiomain():
task = asyncio.create_task(task_loop())
KillNicely(task)
try:
await task
except asyncio.CancelledError:
print("Caught CancelledError", time.time())
await asyncio.sleep(5.0)
raise
class KillNicely:
def __init__(self, cancel_me):
self.cancel_me = cancel_me
self.old_sigint = signal.signal(signal.SIGINT,
self.trap_control_c)
def trap_control_c(self, signum, stack):
if signum != signal.SIGINT:
self.old_sigint(signum, stack)
else:
print("Got Control-C", time.time())
print(self.cancel_me.cancel())
def main():
try:
asyncio.run(aiomain())
except asyncio.CancelledError:
print("Program exit, cancelled", time.time())
if __name__ == '__main__':
main()
您可以看到,您的程序没有运行成功,因为一旦task_loop被取消,它就会退出,而task1和task2没有机会完成。 它们一直都在那里(或者更准确地说,如果程序继续运行,它们本应该在那里)。
这说明了shield()和cancel()之间的交互方式,但它并没有真正解决您所述的问题。 为此,我认为您需要一个可等待对象,以便在关键任务完成之前将程序保持活动状态。 这个对象需要在顶层创建,并向下传递到执行关键任务的地方。 下面是一个类似于您的程序,但表现出期望效果的示例程序。
我进行了三次运行:(1)在task1期间控制-C,(2)在task2期间控制-C,(3)在两个任务完成后控制-C。 在前两种情况下,程序会继续运行,直到task2完成。 在第三种情况下,程序立即结束。
import asyncio
import signal
import time
async def task1():
print("Starting simulated task1", time.time())
await asyncio.sleep(5)
print("Finished simulated task1", time.time())
async def task2():
print("Starting simulated task2", time.time())
await asyncio.sleep(5)
print("Finished simulated task2", time.time())
async def tasks(kwrap):
fut = asyncio.get_running_loop().create_future()
kwrap.awaitable = fut
await task1()
await task2()
fut.set_result(1)
async def task_loop(kwrap):
try:
while True:
await asyncio.shield(tasks(kwrap))
await asyncio.sleep(60)
except asyncio.CancelledError:
print("Shutting down task loop", time.time())
raise
async def aiomain():
kwrap = KillWrapper()
task = asyncio.create_task(task_loop(kwrap))
KillNicely(task)
try:
await task
except asyncio.CancelledError:
print("Caught CancelledError", time.time())
await kwrap.awaitable
raise
class KillNicely:
def __init__(self, cancel_me):
self.cancel_me = cancel_me
self.old_sigint = signal.signal(signal.SIGINT,
self.trap_control_c)
def trap_control_c(self, signum, stack):
if signum != signal.SIGINT:
self.old_sigint(signum, stack)
else:
print("Got Control-C", time.time())
print(self.cancel_me.cancel())
class KillWrapper:
def __init__(self):
self.awaitable = asyncio.get_running_loop().create_future()
self.awaitable.set_result(0)
def main():
try:
asyncio.run(aiomain())
except asyncio.CancelledError:
print("Program exit, cancelled", time.time())
shield
周围的包装器中。 - Daniel Schepler