Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this exception immediately raised from an asyncio Task?

My understanding from the documentation is that asyncio.Tasks, as an asyncio.Future subclass, will store exceptions raised in them and they can be retrieved at my leisure.

However, in this sample code, the exception is raised immediately:

import asyncio

async def bad_task():
    raise Exception()

async def test():
    loop = asyncio.get_event_loop()
    task = loop.create_task(bad_task())
    await task

    # I would expect to get here
    exp = task.exception()
    # but we never do because the function exits on line 3

loop = asyncio.get_event_loop()
loop.run_until_complete(test())
loop.close()

Example output (Python 3.6.5):

python3 ./test.py
Traceback (most recent call last):
  File "./test.py", line 15, in <module>
    loop.run_until_complete(test())
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py", line 468, in run_until_complete
    return future.result()
  File "./test.py", line 9, in test
    await task
  File "./test.py", line 4, in bad_task
    raise Exception()
Exception

Is this a quirk of creating & calling tasks when already within async code?

like image 989
Daniel Drexler Avatar asked Dec 08 '25 06:12

Daniel Drexler


2 Answers

await will raise any exception thrown by the task, because it's meant to make asynchronous code look almost exactly like synchronous code. If you want to catch them, you can use a normal try...except clause.

like image 67
Matti Virkkunen Avatar answered Dec 10 '25 01:12

Matti Virkkunen


As Matti explained, exceptions raised by a coroutine are propagated to the awaiting site. This is intentional, as it ensures that errors do not pass silently by default. However, if one needs to do so, it is definitely possible to await a task's completion without immediately accessing its result/exception.

Here is a simple and efficient way to do so, by using a small intermediate Future:

async def test():
    loop = asyncio.get_event_loop()
    task = loop.create_task(bad_task())
    task_done = loop.create_future()  # you could also use asyncio.Event
    # Arrange for task_done to complete once task completes.
    task.add_done_callback(task_done.set_result)

    # Wait for the task to complete. Since we're not obtaining its
    # result, this won't raise no matter what bad_task() does...
    await task_done
    # ...and this will work as expected.
    exp = task.exception()
like image 33
user4815162342 Avatar answered Dec 10 '25 02:12

user4815162342



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!