To stop the following code one will need to press Ctrl-C
twice, however the second raised KeyboardInterrupt
exception is not catched by the inner try-catch
inside the start_consuming
method.
import asyncio
async def start_consuming():
try:
await asyncio.Future()
except:
try:
await asyncio.Future()
except:
pass
async def main():
await asyncio.gather(start_consuming())
if __name__ == '__main__':
asyncio.run(main())
Why?
It is not like it seems. Here is your program with some debug prints added.
import asyncio
async def start_consuming():
try:
await asyncio.Future()
except BaseException as b:
print(1, repr(b))
try:
await asyncio.Future()
except BaseException as b:
print(2, repr(b))
async def main():
try:
await asyncio.gather(start_consuming())
except BaseException as b:
print(3, repr(b))
if __name__ == '__main__':
try:
asyncio.run(main())
except BaseException as b:
print (4, repr(b))
The output slightly edited:
^C 1 CancelledError()
^C 2 CancelledError()
3 CancelledError()
4 KeyboardInterrupt()
I don't have a fully detailed explanation, but it is clear that when the ctrl-C interrupts arrive, the start_consuming
coroutine is not executing, because it is not ready.
The interrupt (exception) is then caught ("catched") by the asyncio itself. Everything else is in my opinion an asyncio shutdown & cleanup in action. The ctrl-C interrupt is then raised by asyncio.run()
during the exit from asyncio.
I'd like to add a note regarding the asyncio shutdown, "swallowing" the cancellation exception - which is also BaseException
- is against the rules. Docs: "This exception can be caught to perform custom operations when asyncio Tasks are cancelled. In almost all situations the exception must be re-raised."