You can develop a task that cancels itself by first getting access to the asyncio.Task instance using asyncio.current_task(), then calling the cancel() method.
In this tutorial, you will discover how an asyncio task can cancel itself in Python.
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 return 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.
Task Needs to Cancel Itself
Sometimes we may need a task to cancel itself.
This may be for many reasons.
Most commonly, some exceptional events may have occurred. Instead of propagating the exception we want the task to be canceled, and importantly, to have the “cancelled” state when inspected externally.
Alternatively, a complex task may catch and handle a CancelledError exception, then not want to propagate the CancelledError until the next time it resumes.
Can a task cancel itself?
How can we implement a task that cancels itself?
Asyncio Tasks Can Cancel Itself
An asyncio task can cancel itself.
This requires that the task has or retrieves a reference to its own asyncio.Task instance then calls the cancel() method.
The next time the task is suspended, a CancelledError will be raised.
If the task does not suspend, then a CancelledError will be raised when it terminates.
Any subtasks of the current task will also be canceled automatically, as long as they are being awaited by the task that canceled itself.
A task can get access to its own asyncio.Task instance via the asyncio.current_task() function.
For example:
1 2 3 |
... # get the current task task = asyncio.current_task() |
You can learn more about getting the current task in the tutorial:
We can then cancel the task.
1 2 3 |
... # cancel the current task task.cancel() |
Or, in one line:
1 2 3 |
... # cancel the current task asyncio.current_task().cancel() |
And that’s all there is to it.
Next, let’s look at some examples of tasks canceling themselves.
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 of Asyncio Task Canceling Itself
We can explore an example of a task that cancels itself which takes effect the next time it suspends.
In this example, we will create a task and await it in the main coroutine. The task will report a message, sleep, and then cancel itself. It will then sleep again which will cause the CancelledError exception to be propagated and observed in the main coroutine.
Firstly, we can define the task that cancels itself.
The task reports a message, sleeps, cancels itself, sleeps again, and then reports a final message.
The work() coroutine below implements this.
1 2 3 4 5 6 7 8 9 10 11 12 |
# task that take a long time async def work(): # report a message print('Task is starting') # block a moment await asyncio.sleep(0.5) # task cancels itself asyncio.current_task().cancel() # block a moment await asyncio.sleep(0.5) # report a message print('Task is done') |
We don’t expect the final message to be reported as the CancelledError will be raised and propagate on the second sleep.
Next, the main() coroutine can be defined that creates the work() task in the background.
It then awaits it within a try-except block and checks for a CancelledError exception.
1 2 3 4 5 6 7 8 9 10 |
# main coroutine async def main(): # create and schedule the task task = asyncio.create_task(work()) # await the task try: await task except asyncio.CancelledError: print('Main saw the task cancel') print('Main is done') |
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 an asyncio task canceling itself import asyncio # task that take a long time async def work(): # report a message print('Task is starting') # block a moment await asyncio.sleep(0.5) # task cancels itself asyncio.current_task().cancel() # block a moment await asyncio.sleep(0.5) # report a message print('Task is done') # main coroutine async def main(): # create and schedule the task task = asyncio.create_task(work()) # await the task try: await task except asyncio.CancelledError: print('Main saw the task cancel') print('Main is done') # start the event loop asyncio.run(main()) |
Running the example first creates and runs the main() coroutine.
The main() coroutine creates and runs the work() coroutine in the background, and then awaits it.
The work() coroutine runs and reports a message then suspends with a sleep.
The work() coroutine resumes and cancels itself. It then suspends again with a second sleep.
The CancelledError is raised and bubbles up, terminating the work() coroutine.
The main() coroutine resumes and handles the CancelledError reporting a cancellation message, and then reports the final message.
This highlights how a task can cancel itself and how the CancelledError is not raised until the next time the task is suspended.
1 2 3 |
Task is starting Main saw the task cancel Main is done |
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
Example of Task Canceling Itself on Termination
We can explore an example of a task that cancels itself that takes effect when the task is terminated.
In this case, we will update the above example so that it does not explicitly sleep after canceling itself.
The updated work() coroutine with this change is listed below.
1 2 3 4 5 6 7 8 |
# task that take a long time async def work(): # report a message print('Task is starting') # task cancels itself asyncio.current_task().cancel() # report a message print('Task is done') |
This will prevent the CancelledError from rising in the work() coroutine while running.
Instead, the CancelledError will propagate after the work() coroutine terminates.
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 |
# SuperFastPython.com # example of an asyncio task canceling itself on termination import asyncio # task that take a long time async def work(): # report a message print('Task is starting') # task cancels itself asyncio.current_task().cancel() # report a message print('Task is done') # main coroutine async def main(): # create and schedule the task task = asyncio.create_task(work()) # await the task try: await task except asyncio.CancelledError: print('Main saw the task cancel') print('Main is done') # start the event loop asyncio.run(main()) |
Running the example first creates and runs the main() coroutine.
The main() coroutine creates and runs the work() coroutine in the background, and then awaits it.
The work() coroutine runs and reports a message then suspends with a sleep.
The work() coroutine resumes and cancels itself. It then reports a second message and then terminates.
Once the work() coroutine is done, it raises a CancelledError exception, which propagates up to the parent task.
The main() coroutine resumes and handles the CancelledError reporting a cancellation message, and then reports the final message.
This highlights how a task can cancel itself and the CancelledError exception is not propagated until it terminates.
1 2 3 4 |
Task is starting Task is done Main saw the task cancel Main is done |
Example of Task With Subtask Canceling Itself
We can explore an example of a task that cancels itself and also cancels a subtask.
In this case, we will update the above example so that the work() coroutine creates a long-running coroutine to run in the background.
It will then cancel itself and sleep, allowing the CancelledError to be raised and propagate, terminating itself and its subtask.
Firstly, we can define the subtask. This task will sleep for 10 seconds and handles the CancelledError exception in order to report a message, so we can see when it is canceled, then re-raise the CancelledError exception.
The work2() coroutine below implements this.
1 2 3 4 5 6 7 8 9 10 11 12 |
# task that take a long time async def work2(): try: # sleep a long time await asyncio.sleep(10) # report a normal message print('Subtask sleep completed normally') except asyncio.CancelledError: # report a message print('Subtask is cancelled') # re-raise the cancellation raise |
Next, we can update the work() coroutine to create and issue the work2() coroutine in the background.
It will then await the work2() task after canceling itself, allowing the CancelledError to propagate down to the subtask.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# task that take a long time async def work(): # report a message print('Task is starting') # issue a long running task task = asyncio.create_task(work2()) # block a moment await asyncio.sleep(0.5) # task cancels itself asyncio.current_task().cancel() # wait for the child task to complete await task # report a message print('Task is done') |
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# SuperFastPython.com # example of an asyncio task with a subtask canceling itself import asyncio # task that take a long time async def work2(): try: # sleep a long time await asyncio.sleep(10) # report a normal message print('Subtask sleep completed normally') except asyncio.CancelledError: # report a message print('Subtask is cancelled') # re-raise the cancellation raise # task that take a long time async def work(): # report a message print('Task is starting') # issue a long running task task = asyncio.create_task(work2()) # block a moment await asyncio.sleep(0.5) # task cancels itself asyncio.current_task().cancel() # wait for the child task to complete await task # report a message print('Task is done') # main coroutine async def main(): # create and schedule the task task = asyncio.create_task(work()) # await the task try: await task except asyncio.CancelledError: print('Main saw the task cancel') print('Main is done') # start the event loop asyncio.run(main()) |
Running the example first creates and runs the main() coroutine.
The main() coroutine creates and runs the work() coroutine in the background, and then awaits it.
The work() coroutine runs and reports a message. It then creates and schedules the work2() coroutine before suspending with a sleep.
The work2() coroutine runs and suspends with a sleep.
The CancelledError first propagates down and cancels the awaited subtask. The CancelledError exception is raised in the work2() coroutine, which is handled, reporting a message, then re-raised.
The CancelledError exception bubbles up terminating the work() coroutine.
The main() coroutine resumes and handles the CancelledError reporting a cancellation message, and then reports the final message.
This highlights how a task can cancel itself and cancel its subtasks if the subtask is explicitly awaited after the request to self-cancel.
Note, if we did not await work2() in work() and instead left it to run in the background, then it would be free to resume or terminate (be canceled) when the event loop is terminated.
1 2 3 4 |
Task is starting Subtask is cancelled Main saw the task cancel 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 an asyncio task can cancel itself in Python.
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 Chris Weiher on Unsplash
Do you have any questions?