Daniel Drexler
Daniel Drexler

Reputation: 537

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?

Upvotes: 2

Views: 1346

Answers (2)

user4815162342
user4815162342

Reputation: 155630

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()

Upvotes: 3

Matti Virkkunen
Matti Virkkunen

Reputation: 65166

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.

Upvotes: 6

Related Questions