Это очень хороший вопрос. Я кое-что узнал, пока работал над ответом, поэтому надеюсь, что вы все еще следите за этой веткой.
Первое, что нужно исследовать, - как работает метод 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 завершит sh. Но task_b продолжает работать. Внешняя задача (a) останавливается, а внутренняя задача (b) - нет.
Вот модифицированная версия вашей программы, которая это иллюстрирует. Основное изменение состоит в том, чтобы вставить ожидание в обработчик исключения CancelledError, чтобы ваша программа работала на несколько секунд дольше. Я использую 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())
# Output when ctrlC is struck during task1
#
# Starting simulated task1 1590871747.8977509
# Got Control-C 1590871750.8385916
# True
# Shutting down task loop 1590871750.8425908
# Caught CancelledError 1590871750.8435903
# Finished simulated task1 1590871752.908434
# Starting simulated task2 1590871752.908434
# Program exit, cancelled 1590871755.8488846
if __name__ == '__main__':
main()
Вы можете видеть, что ваша программа не работала, потому что она завершилась, как только task_l oop была отменена, до того, как task1 и task2 имели возможность фини sh. Они все еще были там (или, скорее, они были бы там, если бы программа продолжала работать).
Это иллюстрирует, как взаимодействуют shield () и cancel (), но на самом деле это не решает ваши заявленные проблема. Для этого, я думаю, вам нужен ожидаемый объект, который вы можете использовать, чтобы поддерживать программу в рабочем состоянии до тех пор, пока не будут завершены жизненно важные задачи. Этот объект нужно создать на верхнем уровне и передать вниз по стеку туда, где выполняются жизненно важные задачи. Вот программа, похожая на вашу, но преформируется так, как вы хотите.
Я выполнил три запуска: (1) control- C во время задачи1, (2) control- C во время задачи2, (3) control- C после завершения обеих задач. В первых двух случаях программа продолжалась до завершения задачи 2. В третьем случае он закончился немедленно.
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())
# Run 1 Control-C during task1
# Starting simulated task1 1590872408.6737766
# Got Control-C 1590872410.7344952
# True
# Shutting down task loop 1590872410.7354996
# Caught CancelledError 1590872410.7354996
# Finished simulated task1 1590872413.6747622
# Starting simulated task2 1590872413.6747622
# Finished simulated task2 1590872418.6750958
# Program exit, cancelled 1590872418.6750958
#
# Run 1 Control-C during task2
# Starting simulated task1 1590872492.927735
# Finished simulated task1 1590872497.9280624
# Starting simulated task2 1590872497.9280624
# Got Control-C 1590872499.5973852
# True
# Shutting down task loop 1590872499.5983844
# Caught CancelledError 1590872499.5983844
# Finished simulated task2 1590872502.9274273
# Program exit, cancelled 1590872502.9287038
#
# Run 1 Control-C after task2 -> immediate exit
# Starting simulated task1 1590873694.2925708
# Finished simulated task1 1590873699.2928336
# Starting simulated task2 1590873699.2928336
# Finished simulated task2 1590873704.2938952
# Got Control-C 1590873706.0790765
# True
# Shutting down task loop 1590873706.0804725
# Caught CancelledError 1590873706.0804725
# Program exit, cancelled 1590873706.0814824