Error triggers another tasks except-clause

Hi,
I’m having a hard time understanding the behaviour of the following piece of code.

import asyncio

async def successful_task():
    print("Starting successful task")
    try:
        await asyncio.sleep(1)
    except:
        print("This line should never be reached.")

async def unsuccessful_task():
    await asyncio.sleep(0.1)
    raise ValueError("Task unsuccessful.")

async def run_tasks():
    await asyncio.gather(successful_task(), unsuccessful_task())

async def main():
    task = asyncio.create_task(run_tasks())
    await asyncio.wait([task])
    if task.exception():
        pass

asyncio.run(main())

It will print out

Starting successful task
This line should never be reached.

and I’m confused about why the except-clause in successful_task() is triggered.

It’s triggered because except without a matching exception catches any exception.
In this case it catches the asyncio.exceptions.CancelledError exception raised by unsuccesful_task.
The sequence of events is more or less this

  • successful starts and sleeps for 1s
  • unsuccesful starts and sleeps for 0.1s
  • unsuccesful resumes, succesful still sleeping
  • ValueError raises
  • asyncio.exceptions.CancelledError is raised
  • succesful except clause is invoked
  • end
2 Likes

The thing I don’t understand is how that particular except can be triggered, as the matching try is not causing an exception. The print-statement shows that the except-clause is triggered by an exception happening in a completely different place in the code.

Sorry, I skipped some details in my previous response.
Between the two lines
ValueError raises and asyncio.exceptions.CancelledError is raised the sequence of events is
(roughly):

  • when unsuccesful_task() raises ValueError, the exception is sent to run_tasks(), terminating it.
  • def_main() also completes so the run loop is closed, sending a cancellation to all remaining tasks (i.e. successful_task).
  • Cancellation raises a CancelledError
    If def main() would run a bit longer, successful_task will complete normally and no exception will be raised.
    Changing def_main as follows should allow successful_task to complete without exceptions
async def main():
    task = asyncio.create_task(run_tasks())
    await asyncio.wait([task])
    if task.exception():
        pass
    await asyncio.sleep(2)
1 Like

I see, now I get it and can confirm it in my own code by modifying successful_task() to

async def successful_task():
    print("Starting successful task")
    try:
        await asyncio.sleep(1)
    except asyncio.exceptions.CancelledError as e:
        print(f"Caught an exception in successful_task")

Thank you for the help!