You can suppress CancelledError exceptions in asyncio by handling them within the task or in the caller that canceled the task.
Alternatively, they can be suppressed in one line by awaiting asyncio.gather() and configuring it to return exceptions, or via the contextlib.suppress() context manager.
In this tutorial, you will discover how to suppress the CancelledError in asyncio programs.
Let’s get started.
What is Asyncio Task Cancellation?
Asyncio tasks can be canceled.
This can be achieved by calling the cancel() method on the asyncio.Task. This will request that the task be canceled as soon as possible and returns True if the request was successful or False if it was not, e.g. the task is already done.
Request the Task to be cancelled. This arranges for a CancelledError exception to be thrown into the wrapped coroutine on the next cycle of the event loop.
— Coroutines and Tasks
For example:
1 2 3 4 |
... # cancel a task if task.cancel(): # request successful |
The next time that the target task that was canceled resumes, a CancelledError exception will be raised.
This exception will bubble up to the top level of the task and cause it to stop running.
You can learn more about the mechanics of cancellation a task in the tutorial:
You can also learn more about task cancellation best practices in the tutorial:
Run loops using all CPUs, download your FREE book to learn how.
Need to Suppress the CancelledError When Canceling Tasks
When an asyncio task is canceled it will raise a CancelledError the next time the task resumes.
The raised CancelledError can be a problem, for example:
- The CancelledError may propagate up and terminate the target task.
- The CancelledError may propagate up and terminate the calling task.
- The CancelledError may continue to propagate and terminate the program.
You can learn more about how the CancelledError propagates in the tutorial:
If we know that a target task is canceled and will raise a CancelledError exception, we may not be so interested in handling the CancelledError exception.
In turn, we may want to explicitly suppress the expected CancelledError exception in a canceled task.
Given that we may cancel tasks many times in our program, the repeated boilerplate for handling task cancellation may become undesirable.
How can we suppress the CancelledError when canceling a task?
How to Suppress CancelledError
There are perhaps four ways to handle or suppress the CancelledError when canceling a task, they are:
- Suppress within task
- Suppress within caller
- Suppress using asyncio.gather()
- Suppress using contextlib.suppress()
Let’s take a closer look at each in turn.
Suppress CancelledError within Task
A CancelledError exception will be raised within a task when it is canceled.
The task itself can suppress the raised CancelledError exception.
This can be achieved with a try-except block around the body of the task that handles the CancelledError and then does not re-raise it.
For example:
1 2 3 4 5 6 |
... try: # body of the task ... except asyncio.CancelledError: pass |
This will ensure that the task will terminate, e.g. stop executing the task body, but does not raise a CancelledError exception.
Note that this approach will not work if the task is canceled while it is pending. In this case, the CancelledError exception will still be raised.
Suppress CancelledError within Caller
A CancelledError exception can be suppressed by the caller of the task.
That is the task above the canceled task that is controlling it or that has requested that it be canceled.
This can be achieved by adding a try-except around the awaiting the task.
For example:
1 2 3 4 5 6 |
... try: # await the cancelled task await task except asyncio.CancelledError: pass |
This is a common pattern when canceling tasks in asyncio programs, called “cancel and wait”.
You can learn more about the cancel and wait pattern in the tutorial:
This will ensure that the CancelledError exception is suppressed at the appropriate scope and may be the most common way to handle a CancelledError exception.
Suppress CancelledError with asyncio.gather()
A CancelledError exception can be suppressed by using asyncio.gather().
The asyncio.gather() function takes one or more coroutines or tasks and returns once they are done. A list of return values is then returned from each provided task.
A “return_exceptions” argument can be provided and set to True. This will cause the asyncio.gather() function to catch any CancelledError exception raised by tasks and return them in the list of return values.
For example:
1 2 3 |
... # suppress CancelledError exception _ = asyncio.gather(task, return_exceptions=True) |
This provides a simple one-liner for handling a CancelledError exception in a task.
You can learn more about how to use the asyncio.gather() function in the tutorial:
Suppress CancelledError with contextlib.suppress()
We can suppress CancelledError exceptions using contextlib.suppress().
The contextlib.suppress() is a context manager that takes one or more exceptions as arguments and explicitly suppresses them within the body of the context manager.
Return a context manager that suppresses any of the specified exceptions if they occur in the body of a with statement and then resumes execution with the first statement following the end of the with statement.
— contextlib — Utilities for with-statement contexts
We can use this context manager when awaiting a canceled task instead of a try-except block.
For example:
1 2 3 4 |
... # await the cancelled task with contextlib.suppress(asyncio.CancelledError): await task |
This is a preferred approach to suppress the CancelledError exception if the caller has no plans to do anything with the exception, e.g. does not plan to log or do cleanup.
Danger of Suppressing CancelledError
Suppressing CancelledError exceptions can be dangerous.
Specifically, suppressing CancelledError within a target task that has been canceled is discouraged by the asyncio documentation.
… suppressing cancellation completely is not common and is actively discouraged.
— Coroutines and Tasks
This is because the expectation of the cancel() method called on a task is that the target task will raise a CancelledError exception.
If this does not occur, then the expectation or contract with the cancel() method has been broken.
Generally, suppressing the CancelledError exception should occur outside of the target task that is being handled.
The most common point for handling and suppressing a CancelledError exception is at the scope where the task was requested to cancel, e.g. where the cancel() method was called.
Now that we know how to suppress CancelledError exceptions, let’s look at some worked examples.
Free Python Asyncio Course
Download your FREE Asyncio PDF cheat sheet and get BONUS access to my free 7-day crash course on the Asyncio API.
Discover how to use the Python asyncio module including how to define, create, and run new coroutines and how to use non-blocking I/O.
Example Suppressing CancelledError Within Task
We can explore an example of how to suppress a CancelledError exception from within the task.
In this example, we will define a task that takes a long time to complete and then report a message. The task body is protected with a try-except for the CancelledError. The main coroutine schedules the task to run in the background, sleeps a while to allow the task to complete, then cancels the task and waits for the task to cancel before reporting a final message.
Firstly, we can define the long-running task that suppresses the CancelledError when it is canceled.
1 2 3 4 5 6 7 8 9 10 |
# task that take a long time async def work(): try: # sleep a long time await asyncio.sleep(10) # report a final message print('Task sleep completed normally') except asyncio.CancelledError: # suppress CancelledError pass |
Next, we can define the main coroutine that schedules the work() task, allows it to run, and then cancels it.
1 2 3 4 5 6 7 8 9 10 11 12 |
# main coroutine async def main(): # create and schedule the task task = asyncio.create_task(work()) # allow the task to start await asyncio.sleep(2) # cancel the task task.cancel() # await the task await task # report a final message print('Main is done') |
Finally, we can start the event loop and execute the main() coroutine.
1 2 3 |
... # start the event loop asyncio.run(main()) |
Tying this together, the complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# SuperFastPython.com # example of suppressing CancelledError within the task import asyncio # task that take a long time async def work(): try: # sleep a long time await asyncio.sleep(10) # report a final message print('Task sleep completed normally') except asyncio.CancelledError: # suppress CancelledError pass # main coroutine async def main(): # create and schedule the task task = asyncio.create_task(work()) # allow the task to start await asyncio.sleep(2) # cancel the task task.cancel() # await the task await task # report a final message print('Main is done') # start the event loop asyncio.run(main()) |
Running the example first starts the event loop and then runs the main() coroutine.
The main() coroutine runs and schedules the work() coroutine as a task.
It then suspends for 2 seconds, allowing the work task to run.
The work() task runs and suspends with a 10-second sleep.
The main() coroutine resumes and cancels the work() task, it then suspends and waits for the work() task to be done.
A CancelledError exception is raised within the work() task. It is handled and no action is taken. The work() task is then terminated.
The main() coroutine resumes and reports a final message.
This highlights how we can suppress a CancelledError exception from within the canceled task.
1 |
Main is done |
Next, let’s look at an example of suppressing the CancelledError exception at the point where the target task is canceled.
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
Example Suppressing CancelledError Within Caller
We can explore an example of how to suppress a CancelledError exception from the caller of the task that requested the task to be canceled.
In this case, we can update the above example so that the work() coroutine no longer handles the CancelledError exception.
1 2 3 4 5 6 |
# task that take a long time async def work(): # sleep a long time await asyncio.sleep(10) # report a final message print('Task sleep completed normally') |
We can then update the main() coroutine so that when the canceled task is awaited, it handles an expected CancelledError exception, which is then ignored.
1 2 3 4 5 6 7 8 9 |
... # cancel the task task.cancel() try: # await the task await task except asyncio.CancelledError: # suppress CancelledError pass |
Tying this together, the complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# SuperFastPython.com # example of suppressing CancelledError in caller import asyncio # task that take a long time async def work(): # sleep a long time await asyncio.sleep(10) # report a final message print('Task sleep completed normally') # main coroutine async def main(): # create and schedule the task task = asyncio.create_task(work()) # allow the task to start await asyncio.sleep(2) # cancel the task task.cancel() try: # await the task await task except asyncio.CancelledError: # suppress CancelledError pass # report a final message print('Main is done') # start the event loop asyncio.run(main()) |
Running the example first starts the event loop and then runs the main() coroutine.
The main() coroutine runs and schedules the work() coroutine as a task.
It then suspends for 2 seconds, allowing the work task to run.
The work() task runs and suspends with a 10-second sleep.
The main() coroutine resumes and cancels the work() task, it then suspends and waits for the work() task to be done.
A CancelledError exception is raised within the work() and bubbles up, causing the task to terminate.
The main() coroutine resumes and the CancelledError exception is raised. It is then handled and suppressed.
Finally, the main() coroutine reports a final message.
This highlights how we can suppress a CancelledError exception from the caller that canceled the target task.
1 |
Main is done |
Next, let’s look at an example of suppressing a CancelledError exception using asyncio.gather().
Example Suppressing CancelledError via asyncio.gather()
We can explore an example of how to suppress a CancelledError exception using the asyncio.gather() function.
In this case, we can update the above example so that instead of explicitly awaiting the canceled task, the main() coroutine can await a call to asyncio.gather() that takes the canceled task as an argument and is configured to return exceptions.
1 2 3 |
... # suppress CancelledError _ = asyncio.gather(task, return_exceptions=True) |
Tying this together, the complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# SuperFastPython.com # example of suppressing CancelledError with asyncio.gather() import contextlib import asyncio # task that take a long time async def work(): # sleep a long time await asyncio.sleep(10) # report a final message print('Task sleep completed normally') # main coroutine async def main(): # create and schedule the task task = asyncio.create_task(work()) # allow the task to start await asyncio.sleep(2) # cancel the task task.cancel() # suppress CancelledError _ = asyncio.gather(task, return_exceptions=True) # report a final message print('Main is done') # start the event loop asyncio.run(main()) |
Running the example first starts the event loop and then runs the main() coroutine.
The main() coroutine runs and schedules the work() coroutine as a task.
It then suspends for 2 seconds, allowing the work task to run.
The work() task runs and suspends with a 10-second sleep.
The main() coroutine resumes and cancels the work() task, it then suspends and waits for the work() task to be done via asyncio.gather().
A CancelledError exception is raised within the work() and bubbles up, causing the task to terminate.
The main() coroutine resumes and the CancelledError exception is raised in the asyncio.gather() function. It is then handled, suppressed, and returned.
Finally, the main() coroutine reports a final message.
This highlights how we can suppress a CancelledError exception from the caller via asyncio.gather() function.
1 |
Main is done |
Next, let’s look at an example of suppressing a CancelledError exception using contextlib.suppress().
Example Suppressing CancelledError via contextlib.suppress()
We can explore an example of how to suppress a CancelledError exception using the contextlib.suppress() context manager.
In this case, we can update the above example to use the contextlib.suppress() context when awaiting the canceled task.
1 2 3 4 5 |
... # suppress CancelledError with contextlib.suppress(asyncio.CancelledError): # await the task await task |
Tying this together, the complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# SuperFastPython.com # example of suppressing CancelledError with contextlib.suppress import contextlib import asyncio # task that take a long time async def work(): # sleep a long time await asyncio.sleep(10) # report a final message print('Task sleep completed normally') # main coroutine async def main(): # create and schedule the task task = asyncio.create_task(work()) # allow the task to start await asyncio.sleep(2) # cancel the task task.cancel() # suppress CancelledError with contextlib.suppress(asyncio.CancelledError): # await the task await task # report a final message print('Main is done') # start the event loop asyncio.run(main()) |
Running the example first starts the event loop and then runs the main() coroutine.
The main() coroutine runs and schedules the work() coroutine as a task.
It then suspends for 2 seconds, allowing the work task to run.
The work() task runs and suspends with a 10-second sleep.
The main() coroutine resumes and cancels the work() task, it then suspends and waits for the work() task to be done via asyncio.gather().
A CancelledError exception is raised within the work() and bubbles up, causing the task to terminate.
The main() coroutine resumes and the CancelledError exception is raised and then suppressed by the contextlib.suppress() context manager.
The main() coroutine resumes and exits the context manager and then reports a final message.
This highlights how we can suppress a CancelledError exception from the caller via the contextlib.suppress() context manager.
1 |
Main is done |
Further Reading
This section provides additional resources that you may find helpful.
Python Asyncio Books
- Python Asyncio Mastery, Jason Brownlee (my book!)
- Python Asyncio Jump-Start, Jason Brownlee.
- Python Asyncio Interview Questions, Jason Brownlee.
- Asyncio Module API Cheat Sheet
I also recommend the following books:
- Python Concurrency with asyncio, Matthew Fowler, 2022.
- Using Asyncio in Python, Caleb Hattingh, 2020.
- asyncio Recipes, Mohamed Mustapha Tahrioui, 2019.
Guides
APIs
- asyncio — Asynchronous I/O
- Asyncio Coroutines and Tasks
- Asyncio Streams
- Asyncio Subprocesses
- Asyncio Queues
- Asyncio Synchronization Primitives
References
Takeaways
You now know how to suppress CancelledError exceptions in asyncio programs.
Did I make a mistake? See a typo?
I’m a simple humble human. Correct me, please!
Do you have any additional tips?
I’d love to hear about them!
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Photo by Nick Baker on Unsplash
Do you have any questions?