I have an asyncio/Python program with two asyncio tasks:
I want my entire program to exit after the first crash. I cannot get it to happen.
import asyncio
import time
def infinite_while():
while True:
time.sleep(1)
async def task_1():
await asyncio.sleep(1)
assert False
async def task_2():
loop = asyncio.get_event_loop()
await loop.run_in_executor(None, lambda: infinite_while())
loop = asyncio.get_event_loop()
asyncio.set_event_loop(loop)
tasks = asyncio.gather(task_2(), task_1())
try:
loop.run_until_complete(tasks)
except (Exception, KeyboardInterrupt) as e:
print('ERROR', str(e))
exit()
It prints ERROR but does not exit. When manually closed, the program prints the following stack trace:
Error in atexit._run_exitfuncs:
Traceback (most recent call last):
File "/usr/lib/python3.5/concurrent/futures/thread.py", line 39, in _python_exit
t.join()
File "/usr/lib/python3.5/threading.py", line 1054, in join
self._wait_for_tstate_lock()
File "/usr/lib/python3.5/threading.py", line 1070, in _wait_for_tstate_lock
elif lock.acquire(block, timeout):
KeyboardInterrupt
Run an asyncio Event Loop run_until_complete(<some Future object>) – this function runs a given Future object, usually a coroutine defined by the async / await pattern, until it's complete. run_forever() – this function runs the loop forever. stop() – the stop function stops a running loop.
sleep in asyncio coroutines unless you want to block the main thread, therefore freezing the event loop and probably the whole application as well.
How many times should Asyncio run () be called? It should be used as a main entry point for asyncio programs, and should ideally only be called once. New in version 3.7.
When an exception is risen in a task, it never propagates to the scope where the task was launched via eventloop, i.e. the loop.run_until_complete(tasks)
call. Think of it, as if the exception is thrown only in the context of your task, and that is the top level scope where you have the chance to handle it, otherwise it will be risen in the "background".
This said, you will never catch an Exception
from the task with this:
try:
loop.run_until_complete(tasks)
except (Exception, KeyboardInterrupt) as e:
print('ERROR', str(e))
exit()
...and that is just how the event loop works. Imagine if you would have a service with several tasks, and one of them would fail, that would stop the whole service.
What you could do is stop the eventloop manually when you catch an exception in task1
, e.g.
async def task_1():
await asyncio.sleep(1)
try:
assert False
except Exception:
# get the eventloop reference somehow
eventloop.stop()
However, this is very dirty and kind of hacky, so I would rather suggest to go with the solution that @D-Von suggested, which is much cleaner and safer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With