Last Updated on November 14, 2023
A coroutine may return a result directly via a return value.
Asyncio tasks that execute a coroutine run asynchronously. Therefore we need a way to retrieve results from coroutines executed by independently run coroutines.
In this tutorial, you will discover how to get a result from an asyncio task.
After completing this tutorial, you will know:
- How to retrieve a result from an asyncio task.
- What happens to the result if the task fails or is cancelled.
- What happens if we try to retrieve a result from a running task.
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 get results from tasks.
Run loops using all CPUs, download your FREE book to learn how.
How to Get Task Result
We can get the result of a task via the result() method.
This method returns the return value of the coroutine wrapped by the Task or None if the wrapped coroutine does not explicitly return a value.
For example:
1 2 3 |
... # get the return value from the wrapped coroutine value = task.result() |
If the coroutine fails with an unhandled exception, it is re-raised when calling the result() method and may need to be handled.
For example:
1 2 3 4 5 6 |
... try: # get the return value from the wrapped coroutine value = task.result() except Exception: # task failed and there is no result |
If the task was canceled, then a CancelledError exception is raised when calling the result() method and may need to be handled.
For example:
1 2 3 4 5 6 |
... try: # get the return value from the wrapped coroutine value = task.result() except asyncio.CancelledError: # task was canceled |
As such, it is a good idea to check if the task was canceled first.
For example:
1 2 3 4 5 6 7 |
... # check if the task was not canceled if not task.cancelled(): # get the return value from the wrapped coroutine value = task.result() else: # task was canceled |
If the task is not yet done, then an InvalidStateError exception is raised when calling the result() method and may need to be handled.
For example:
1 2 3 4 5 6 |
... try: # get the return value from the wrapped coroutine value = task.result() except asyncio.InvalidStateError: # task is not yet done |
As such, it is a good idea to check if the task is done first.
For example:
1 2 3 4 5 6 |
... # check if the task is not done if not task.done(): await task # get the return value from the wrapped coroutine value = task.result() |
Now that we know how to get the return value from a task, let’s look at some worked examples.
Example of Getting a Result From a Done Task
We can explore how to get a return value result from a successfully done task.
In this example, we define a task coroutine that reports a message, blocks for a moment, then returns a value.
We then define the main coroutine that is used as the entry point into the asyncio program. It reports a message, creates and schedules the task, then awaits the task to be completed. Once completed, it retrieves the result from the task and reports it.
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 getting a result from a done task 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) # return a value return 99 # custom coroutine async def main(): # report a message print('main coroutine started') # create and schedule the task task = asyncio.create_task(task_coroutine()) # wait for the task to complete await task # get the result value = task.result() print(f'result: {value}') # report a final message print('main coroutine done') # start the asyncio program asyncio.run(main()) |
Running the example starts the asyncio event loop and executes the main() coroutine.
The main() coroutine reports a message, then creates and schedules the task coroutine.
It then suspends and awaits the task to be completed.
The task runs, reports a message, and sleeps for a moment before returning a value and terminating normally.
The main() coroutine resumes and retrieves the return value result from the task, which is then reported.
This example highlights the normal case of retrieving a return value from a successful task.
1 2 3 4 |
main coroutine started executing the task result: 99 main coroutine done |
Next, we will look at getting a result from a task that does not return a value.
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 Getting a Result From a Task That Does Not Return a Value
If the coroutine that the task wraps does not return a value, then the result() method returns the default return value None.
We can explore getting a result from a task that does not return a value.
In the example below, we update the example from the previous section so that the task coroutine does not return a value.
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 getting a result from a done task that does not return a value 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(): # report a message print('main coroutine started') # create and schedule the task task = asyncio.create_task(task_coroutine()) # wait for the task to complete await task # get the result value = task.result() print(f'result: {value}') # report a final message print('main coroutine done') # start the asyncio program asyncio.run(main()) |
Running the example starts the asyncio event loop and executes the main() coroutine.
The main() coroutine reports a message, then creates and schedules the task coroutine.
It then suspends and awaits the task to be completed.
The task runs, reports a message, and sleeps for a moment before terminating normally without returning a value.
The main() coroutine resumes and attempts to retrieve a return value result from the task, which is then reported.
We can see that the return value for the task is None because it did not explicitly return a value.
This example highlights that we can retrieve a result from a task that does not return a value and expect to receive the value None.
1 2 3 4 |
main coroutine started executing the task result: None main coroutine done |
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
Example of Getting a Result From a Failed Task
If the task fails with an unhandled exception, the exception will be re-raised when calling the result() method on the task to get the result.
As such, we may need to handle possible exceptions when getting task results.
We can explore getting a result from a task that failed with an unhandled exception.
In this example, we can update the task coroutine to explicitly raise an exception that is not handled.
This will cause the task’s coroutine to fail.
The main coroutine will sleep to wait for the task to be completed. This is to avoid using the await expression which will also propagate the exception back to the caller.
You can learn more about handling exceptions in tasks in the tutorial:
Once the task is done, the main coroutine will attempt to retrieve the return value and handle the exception that is re-raised.
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 |
# SuperFastPython.com # example of getting a result from an asyncio task that failed 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) # fail with an exception raise Exception('Something bad happened') # return a value (never reached) return 99 # custom coroutine async def main(): # report a message print('main coroutine started') # create and schedule the task task = asyncio.create_task(task_coroutine()) # wait for the task to complete await asyncio.sleep(1.1) try: # get the result value = task.result() print(f'result: {value}') except Exception as e: print(f'Failed with: {e}') # report a final message print('main coroutine done') # start the asyncio program asyncio.run(main()) |
Running the example starts the asyncio event loop and executes the main() coroutine.
The main() coroutine reports a message, then creates and schedules the task coroutine.
It then suspends and awaits the task to be completed.
The task runs, reports a message, and sleeps for a moment. The task resumes and raises an exception.
The exception does not terminate the application or the asyncio event loop.
Instead, the exception is captured by the asyncio event loop and stored in the task.
The main() coroutine resumes and then attempts to retrieve the result from the task. This fails and the exception that was raised and not handled in the Task‘s wrapped coroutine is re-raised in the caller.
The main() coroutine catches the exception and reports its details.
1 2 3 4 |
main coroutine started executing the task Failed with: Something bad happened main coroutine done |
Next, let’s look at what happens if we try to get a task result from a running task.
Example of Getting a Result From a Running Task
We cannot retrieve results from a running asyncio task.
Instead, we can only retrieve a result from a task after it is done.
If we call the result() method on a task that is scheduled or running, an InvalidStateError exception is raised by the caller.
In the example below, we update the above example so that the main coroutine schedules the task, waits a moment then attempts to get the result too soon before the task has been completed.
This is expected to raise an InvalidStateError.
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 getting a result from a running task 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) # return a value (never reached) return 99 # custom coroutine async def main(): # report a message print('main coroutine started') # create and schedule the task task = asyncio.create_task(task_coroutine()) # wait for the task to complete await asyncio.sleep(0.1) # get the result value = task.result() print(f'result: {value}') # report a final message print('main coroutine done') # start the asyncio program asyncio.run(main()) |
Running the example starts the asyncio event loop and executes the main() coroutine.
The main() coroutine reports a message, then creates and schedules the task coroutine.
It then suspends and sleeps for a moment.
The task runs, reports a message, and sleeps for a moment.
The main() coroutine resumes and attempts to retrieve the result from the task while the task is running, even though the task is suspended.
This fails with an InvalidStateError that breaks the asyncio event loop in this case.
This example highlights that we must always retrieve a Task result after the task is done.
1 2 3 4 5 |
main coroutine started executing the task Traceback (most recent call last): ... asyncio.exceptions.InvalidStateError: Result is not set. |
We can check if a task is done before retrieving the exception via the done() method that will return True if the task is done, or False otherwise.
Next, we can look at the case of attempting to get a task result from a canceled task.
Example of Getting a Result From a Canceled Task
We cannot retrieve a result from a canceled task.
Although a canceled task is done, a result will not be available and cannot be retrieved.
Instead, a CancelledError exception is raised when calling the result() method if the task was canceled.
The example below updates the previous example to create and schedule the task as before, then wait a moment. It then cancels the task, waits a moment for the task to be canceled, then attempts to get the result.
This is expected to fail as the result() method will re-raise the CancelledError exception from the wrapped coroutine that was used to cancel the task.
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 |
# SuperFastPython.com # example of getting a result from a canceled task 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) # return a value (never reached) return 99 # custom coroutine async def main(): # report a message print('main coroutine started') # create and schedule the task task = asyncio.create_task(task_coroutine()) # wait for the task to complete await asyncio.sleep(0.1) # cancel the task task.cancel() # wait a moment for the task to be canceled await asyncio.sleep(0.1) # get the result value = task.result() print(f'result: {value}') # report a final message print('main coroutine done') # start the asyncio program asyncio.run(main()) |
Running the example starts the asyncio event loop and executes the main() coroutine.
The main() coroutine reports a message, then creates and schedules the task coroutine.
It then suspends and sleeps for a moment.
The main() coroutine resumes and cancels the task. It then suspends and waits a moment for the task to respond to the request for being canceled.
The task is canceled by raising a CancelledError within the wrapped coroutine.
The main() coroutine resumes and attempts to retrieve the result.
This fails and the CancelledError exception is re-raised in the caller.
This breaks the event loop in this case.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
main coroutine started executing the task Traceback (most recent call last): ... asyncio.exceptions.CancelledError During handling of the above exception, another exception occurred: Traceback (most recent call last): ... asyncio.exceptions.CancelledError During handling of the above exception, another exception occurred: Traceback (most recent call last): ... asyncio.exceptions.CancelledError |
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 get a result from 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 Hunter Newton on Unsplash
Peter Gossler says
Hi Jason,
Great content and very educational.
I am in the middle of a project automating some maintenance aspects of my marine tank.
So I’m basically designing an IoT device with two Picos. One as a WEB server and the other as the control unit switching relays and measuring various aspects of the tank.
Obviously this is a project for concurrency and threading.
Your content has helped me a lot to get things not only working, but working reliably.
One thing I either missed in your explanations – it might be it is mentioned somewhere and I didn’t find it yet – is how to get data from a Coro. You explain to use the task.result() method, which in most cases will suffice. But I opted to to use a callback function that is called once something actionable happens in the Coro.
This works very well for me and I believe that this methodology should have a mention in your texts.
In any case – I’m greatful for your knowledge transfer.
Regards,
Peter
Jason Brownlee says
Thank you for sharing, that sounds like a great project, and thanks for the feedback.