How to Get Asyncio Task Results - Super Fast Python

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:

...

# 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.

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:

...

# 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:

...

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:

...

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:

...

# 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:

...

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:

...

# 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.

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.

Learn more
 


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.

main coroutine started

executing the task

result: None

main coroutine done

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.

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.


Python Asyncio Jump-Start

Loving The Tutorials?

Why not take the next step? Get the book.

Learn more
 


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.

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

I also recommend the following books:

Guides

APIs

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