Last Updated on November 14, 2023
An asyncio Task is an object that schedules and independently runs an asyncio coroutine.
It provides a handle on a scheduled coroutine that an asyncio program can query and use to interact with the coroutine.
Because tasks are executed asynchronously, we often need to check on the status of the task, such as whether it is done, is still running, or was cancelled.
In this tutorial, you will discover how to check the status of an asyncio task.
After completing this tutorial, you will know:
- How to check if an asyncio task is running or is done.
- How to check if a task was canceled or completed normally.
- Understand the conditions under which we may consider that a task is done.
Let’s get started.
What is an Asyncio Task
An asyncio Task is an object that schedules and independently runs an asyncio coroutine.
It provides a handle on a scheduled coroutine that an asyncio program can query and use to interact with the coroutine.
A Task is an object that manages an independently running coroutine.
— PEP 3156 – Asynchronous IO Support Rebooted: the “asyncio” Module
An asyncio task is represented via an instance of the asyncio.Task class.
A task is created from a coroutine. It requires a coroutine object, wraps the coroutine, schedules it for execution, and provides ways to interact with it.
A task is executed independently. This means it is scheduled in the asyncio event loop and will execute regardless of what else happens in the coroutine that created it. This is different from executing a coroutine directly, where the caller must wait for it to complete.
Tasks are used to schedule coroutines concurrently. When a coroutine is wrapped into a Task with functions like asyncio.create_task() the coroutine is automatically scheduled to run soon
— Coroutines and Tasks
We can create a task using the asyncio.create_task() function.
This function takes a coroutine instance and an optional name for the task and returns an asyncio.Task instance.
Wrap the coro coroutine into a Task and schedule its execution. Return the Task object.
— Coroutines and Tasks
For example:
1 2 3 |
... # create and schedule a task task = asyncio.create_task(coro) |
You can learn more about asyncio tasks in the tutorial:
Now that we know about asyncio tasks, let’s look at how we might check their status.
Run loops using all CPUs, download your FREE book to learn how.
How to Check Task Status
After a Task is created, we can check the status of the task.
There are two statuses we might want to check, they are:
- Whether the task is done.
- Whether the task was canceled.
Let’s take a closer look at each in turn.
Check if a Task is Done
We can check if a task is done via the done() method.
The method returns True if the task is done, or False otherwise.
For example:
1 2 3 4 |
... # check if a task is done if task.done(): # ... |
A task is done if it has had the opportunity to run and is now no longer running.
A task that has been scheduled is not done.
Similarly, a task that is running is not done.
Check if a Task is Canceled
We can check if a task is canceled via the cancelled() method.
The method returns True if the task was canceled, or False otherwise.
For example:
1 2 3 4 |
... # check if a task was canceled if task.cancelled(): # ... |
A task is canceled if the cancel() method was called on the task and completed successfully, e.g. cancel() returned True.
A task is not canceled if the cancel() method was not called, or if the cancel() method was called but failed to cancel the task.
When is an Asyncio Task Done?
A done task means the task is finished.
It means that the task had at least one opportunity to execute and is no longer able to execute.
A task is done if:
- The coroutine finishes normally.
- The coroutine returns explicitly.
- An unexpected error or exception is raised in the coroutine
- The task is canceled.
A scheduled task is not done.
A suspended task is not done.
A sleeping task is not done.
Now that we know how to check the status of a task, 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 of Checking if a Task is Done
We can explore how to check if a task is done.
In this example, we will define a task coroutine that reports a message and sleeps for a moment. We will then create and schedule the task coroutine from the main coroutine.
We will check the status of the task immediately after it is created before it has run. We will then give the task an opportunity to run, then check the status of the task while it is running. Finally, we will check the status of the task after it is finished
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 |
# SuperFastPython.com # example of checking if an asyncio task is done import asyncio # define a coroutine for a task async def task_coroutine(): # report a message print('executing the task') # block for a moment await asyncio.sleep(1) # custom coroutine async def main(): # create and schedule the task task = asyncio.create_task(task_coroutine()) # check if it is done print(f'>task done: {task.done()}') # wait a moment await asyncio.sleep(0.1) # check if it is done print(f'>task done: {task.done()}') # wait for the task to complete await task # check if it is done print(f'>task done: {task.done()}') # start the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and runs it as the entry point to the asyncio program.
The main() coroutine runs, first creating an instance of the task_coroutine() coroutine and then using it to create and schedule a task.
The main() coroutine then checks if the new task is done, which it is not because it has just been scheduled. It then sleeps, giving the task an opportunity to run.
The task begins executing, reporting a message, and then sleeping.
The main() coroutine then resumes and checks if the coroutine is done. At this point, the task_coroutine() is still running, yet suspended in a call to sleep.
The main() coroutine then awaits the task to be completed.
The task_coroutine() finishes its sleep, resumes, and then terminates.
The main() coroutine then continues on and checks the done status one final time. This time the task is done as it has completely finished.
This example highlights that a scheduled and suspended task is not done and that a task is not done until the wrapped coroutine is exited.
1 2 3 4 |
>task done: False executing the task >task done: False >task done: True |
Next, let’s explore how we might poll the done status of a task.
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
Example of Polling a Task Until it is Done
We can explore how to poll the status of a task in a busy-wait loop.
In this example, we will create and schedule a task as before. The main coroutine will loop until the task is complete, each iteration checking and reporting the status of the task and sleeping for a moment if the task is not yet complete.
It is critical that the main coroutine suspend each iteration in order to give other coroutines an opportunity to execute, e.g. to allow the task coroutine an opportunity to run and complete.
Busy wait loops are not a good practice because they consume more resources than simply waiting for an event, but they do give the caller to perform other actions while waiting.
You can learn more about busy wait loops in the tutorial:
The complete example of a busy wait loop for checking the status of an asyncio task 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 polling the status of an asyncio task until it is done import asyncio # define a coroutine for a task async def task_coroutine(): # report a message print('executing the task') # block for a moment await asyncio.sleep(1) # custom coroutine async def main(): # create and schedule the task task = asyncio.create_task(task_coroutine()) # poll the status of the task while True: # get the status of the task status = task.done() # report the status print(f'>task done: {task.done()}') # check if the task is done if status: break # otherwise block for a moment await asyncio.sleep(0.1) # start the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and runs it as the entry point to the asyncio program.
The main() coroutine runs, first creating an instance of the task_coroutine() coroutine and then using it to create and schedule a task.
It then begins to loop forever.
In each iteration, the main() coroutine gets the done status of the task, reports the status, then checks the status. If the task is done, the loop is broken, otherwise, it suspends, sleeping for a fraction of a second.
The task runs, reports its message, and sleeps. Finally, it terminates.
The main() coroutine notices that the task is done, then exits the loop.
1 2 3 4 5 6 7 8 9 10 11 12 |
>task done: False executing the task >task done: False >task done: False >task done: False >task done: False >task done: False >task done: False >task done: False >task done: False >task done: False >task done: True |
Next, let’s explore the done status of a task that fails with an exception.
Example of an Exception Causes a Task to be Done
We can explore the status of a task where the wrapped coroutine fails with an exception.
In this example, we will update the task coroutine to sleep for less time and to then raise an exception.
This will terminate the task and cause the task to be marked as done.
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 checking the status of a task that raises an exception import asyncio # define a coroutine for a task async def task_coroutine(): # report a message print('executing the task') # block for a moment await asyncio.sleep(0.5) # raise an exception raise Exception('Something bad happened') # custom coroutine async def main(): # create and schedule the task task = asyncio.create_task(task_coroutine()) # check if it is done print(f'>task done: {task.done()}') # wait a moment await asyncio.sleep(0.1) # check if it is done print(f'>task done: {task.done()}') # wait for the task to complete await asyncio.sleep(0.5) # check if it is done print(f'>task done: {task.done()}') # start the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and runs it as the entry point to the asyncio program.
The main() coroutine runs, first creating an instance of the task_coroutine() coroutine and then using it to create and schedule a task.
The main() coroutine then checks if the new task is done, which it is not because it has just been scheduled. It then sleeps, giving the task an opportunity to run.
The task begins executing, reporting a message, and then sleeping.
The main() coroutine then resumes and checks if the coroutine is done. At this point, the task_coroutine() is still running, yet suspended in a call to sleep.
The main() coroutine then sleeps for a little longer. We don’t await the task, because it will raise an exception that will need to be handled in our main() coroutine.
The task_coroutine() finishes its sleep and then resumes raising an exception. This terminates the task and marks it as done.
The main() coroutine then continues on and checks the done status one final time. This time the task is done.
The event loop is exited and notices that an exception was raised in a task that was never retrieved. It then logs the exception using a default logger, e.g. to standard output.
This example highlights that an exception raised in a coroutine wrapped in a task will cause the task to be done.
1 2 3 4 5 6 7 8 9 10 |
>task done: False executing the task >task done: False >task done: True Task exception was never retrieved future: <Task finished name='Task-2' coro=<task_coroutine() done, defined at ...> exception=Exception('Something bad happened')> Traceback (most recent call last): File "...", line 12, in task_coroutine raise Exception('Something bad happened') Exception: Something bad happened |
Next, we can look at the done status of a canceled task.
Example of Being Canceled Causes a Task to be Done
A canceled task is marked as done.
We can explore canceling a task and then checking if it is indeed done.
A task can be canceled by calling the cancel() method.
In this example, we create and schedule the task as before. We wait a moment and check its done status while running, then cancel the task. The main coroutine waits for the task to be canceled completely and then checks the done status a final time.
The expectation is a task that is canceled and is given an opportunity to be canceled (have a CancelledError exception raised within the wrapped coroutine) will be canceled.
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 canceling a task and checking its done status import asyncio # define a coroutine for a task async def task_coroutine(): # report a message print('executing the task') # block for a moment await asyncio.sleep(1) # custom coroutine async def main(): # create and schedule the task task = asyncio.create_task(task_coroutine()) # check if it is done print(f'>task done: {task.done()}') # wait a moment await asyncio.sleep(0.1) # check if it is done print(f'>task done: {task.done()}') # cancel the task task.cancel() # wait a moment more await asyncio.sleep(0.1) # check if it is done print(f'>task done: {task.done()}') # start the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and runs it as the entry point to the asyncio program.
The main() coroutine runs, first creating an instance of the task_coroutine() coroutine and then using it to create and schedule a task.
The main() coroutine then checks if the new task is done, which it is not because it has just been scheduled. It then sleeps, giving the task an opportunity to run.
The task begins executing, reporting a message, and then sleeping.
The main() coroutine then resumes and checks if the coroutine is done. At this point, the task_coroutine() is still running, yet suspended in a call to sleep.
The main() coroutine then cancels the task via a call to the cancel() method.
The main() coroutine then sleeps for a little longer. We don’t await the task, because it will raise a CancelledError exception which will need to be handled in our main() coroutine.
The task_coroutine() is canceled. This terminates the task and marks it as done.
The main() coroutine then resumes and checks the done status one final time. This time the task is done.
1 2 3 4 |
>task done: False executing the task >task done: False >task done: True |
Next, let’s look at how we might check if a task is canceled.
Example of Checking if a Task is Canceled
We can explore how to check if an asyncio task is canceled.
A task is only canceled if a coroutine calls the cancel() method.
A normal task that is not canceled will not have the status of canceled.
The example below demonstrates this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# SuperFastPython.com # example of checking if an asyncio task is canceled import asyncio # define a coroutine for a task async def task_coroutine(): # report a message print('executing the task') # block for a moment await asyncio.sleep(1) # custom coroutine async def main(): # create and schedule the task task = asyncio.create_task(task_coroutine()) # check if it is canceled print(f'>task canceled: {task.cancelled()}') # wait for the task to complete await task # check if it is canceled print(f'>task canceled: {task.cancelled()}') # start the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and runs it as the entry point to the asyncio program.
The main() coroutine runs, first creating an instance of the task_coroutine() coroutine and then using it to create and schedule a task.
The main() coroutine then checks if the new task is canceled, which it is not because it has just been scheduled. It then awaits the task to be done.
The task begins executing, reporting a message, and then sleeping. The task_coroutine() finishes its sleep, resumes, and then terminates.
The main() coroutine resumes and checks the canceled status a final time. This time the task is done, but is not canceled.
This example highlights that a done task does not have the canceled status.
1 2 3 |
>task canceled: False executing the task >task canceled: False |
Next, let’s look at an example of canceling a task and confirming it has a canceled status.
Example of Checking if a Canceled Task was Canceled
We can explore the case of explicitly canceling a task, then confirming that the status of the task is marked as canceled.
In this example, we update the previous example to cancel the task while it is running, wait for the task to be done after being canceled, then check its status.
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 |
# SuperFastPython.com # example of canceling a task then checking if it is canceled import asyncio # define a coroutine for a task async def task_coroutine(): # report a message print('executing the task') # block for a moment await asyncio.sleep(1) # custom coroutine async def main(): # create and schedule the task task = asyncio.create_task(task_coroutine()) # check if it is canceled print(f'>task canceled: {task.cancelled()}') # give the task a chance to run await asyncio.sleep(0.1) # cancel the task task.cancel() # wait for the task to be done await asyncio.sleep(0.1) # check if it is canceled print(f'>task canceled: {task.cancelled()}') # start the asyncio program asyncio.run(main()) |
Running the example first creates the main() coroutine and runs it as the entry point to the asyncio program.
The main() coroutine runs, first creating an instance of the task_coroutine() coroutine and then using it to create and schedule a task.
The main() coroutine then checks if the new task is canceled, which it is not because it has just been scheduled. It is then suspended, sleeping for a fraction of a section.
This gives the task an opportunity to execute, report its message and sleep itself.
The main() coroutine resumes and then cancels the task.
It then sleeps again, suspending and allowing the task to execute and handle the request to be canceled.
The task resumes and completes its cancellation, e.g. a CancelledError exception is raised in the coroutine and is not handled. The task is both done and marked as canceled.
The main() coroutine then checks the canceled status one final time, confirming that indeed the done task is now marked as canceled.
1 2 3 |
>task canceled: False executing the task >task canceled: True |
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 check the status of an asyncio task in Python.
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Photo by Goh Rhy Yan on Unsplash
Do you have any questions?