Суть вопроса неверна. (Я знал это, когда писал, но не тогда, когда у меня изначально была проблема, поэтому я оставил ее в этой форме для других с такой же проблемой.) asyncio.run()
заканчивается , но перепрыгивает через все исключения код обработки и завершения работы:
def main():
# Some setup code ...
try:
asyncio.run(my_coroutine())
except Exception as e:
# <---- In Python 3.7, control passed to here when asyncio.run() finshed
print("Exiting due to exception {}: {}".format(type(e).__name__, e))
print("Coroutine finished")
# Some cleanup code ...
print("Shutdown complete")
# <---- In Python 3.8 it directly dropped out of here!
Это связано с тем, что в Python 3.8 базовый класс asyncio.CancelledError
изменился с Exception
на BaseException
(т.е. базовый класс Exception
). Это было сделано, чтобы избежать ошибок в асин c коде, когда люди ловят Exception
, думая, что это означало, что какая-то операция завершилась неудачно (например, ошибка сети), но случайно предотвратила отмену; см. Python выпуск 32528 .
Приложению не удалось завершиться, поскольку Python ожидает завершения всех потоков до sh, если они не запущены с daemon=False
, переданными в Thread
конструктор (см. Обсуждение нескольких абзацев в Документы по потокам ). В моем случае поток не является потоком демона (потому что я действительно хочу, чтобы он завершился sh изящно), но он не завершится, пока я не запросил его, что делается в коде, в котором я добавляю комментарий "Некоторый код очистки ... ".
Решение состоит в том, чтобы либо явно отлавливать asyncio.CancelledError
в дополнение к Exception
, либо использовать вместо него блок finally:
. Блок finally:
, вероятно, лучше, поскольку он действительно гарантирует выполнение кода, даже несмотря на другие исключения, полученные из BaseException
, такие как KeyboardInterrupt
:
def main():
# Some setup code ...
try:
asyncio.run(my_coroutine())
finally:
print("Coroutine finished")
# Some cleanup code ...
print("Shutdown complete")