InvalidStateError: Result is not set

March 3, 2024 Python Asyncio

You can get an InvalidStateError exception when attempting to retrieve a return value result from an asyncio Task.

This will happen if we retrieve a result via the result() method on a task while the task is still running, e.g. the task is not yet done.

We can avoid the InvalidStateError exception by waiting for the task to be done before attempting to retrieve the result either directly or via a function such as asyncio.gather() or asyncio.wait().

In this tutorial, you will discover why we get the InvalidStateError: "Result is not set" and how to fix it.

Let's get started.

InvalidStateError: Result is not set

It is common to get an InvalidStateError exception when using asyncio.

An InvalidStateError exception is raised with the message:

Developers often get this exception when first getting started with asyncio.

What does this InvalidStateError exception mean?

How can we fix it and avoid it in the future?

Why Do We Get An InvalidStateError

We get an InvalidStateError exception when we try to retrieve a return value result from an asyncio.Task while the task is still running.

This will happen if we call the result() method on an asyncio.Task() instance and the task is not done.

For example:

...
# attempt to get a result from the task
value = task.result()

This is the regular way to get a return value result from a task.

It will return the return value from the Task's coroutine or re-raise an exception if the coroutine failed with an uncancelled exception.

The problem is, we must wait until the task is completed, e.g, has the state "done". This means that the done() method on the task returns True.

You can learn more about retrieving results from tasks in the tutorial:

How to Avoid InvalidStateError

We can avoid an InvalidStateError when calling the result() method by waiting for the task to be done.

There are many ways we can do this, such as:

  1. Await the task directly
  2. Await the task via asyncio.gather()
  3. Await the task via asyncio.wait()

Let's take a closer look at each in turn.

Await The Task Directly

The first approach is to await the task directly.

For example:

...
# wait for the task to be done and get the result
result = await task

This does two things:

  1. Waits for the task to be done.
  2. Retrieves the return value from the task.

We would no longer need to call the result() method on the task. Nevertheless, we can call the result() method if we prefer.

For example:

...
# wait for the task to be done
await task
# attempt to get a result from the task
value = task.result()

You can learn more about awaiting in the tutorial:

Await The Task Via asyncio.gather()

Another approach is to use the asyncio.gather() function.

This function takes one or more awaitables and returns once they are all done.

For example:

...
# wait for the task to be done
_ = await asyncio.gather(task)
# attempt to get a result from the task
value = task.result()

The asyncio.gather() function itself will return a list of return values, so we may want to retrieve the return value from it directly.

For example:

...
# wait for the task to be done
results = await asyncio.gather(task)
# get the result
value = results[0]

You can learn more about how to use the asyncio.gather() function in the tutorial:

Await The Task Via asyncio.wait()

Another similar approach is to use the asyncio.wait() function.

This function takes a collection of tasks and returns once a condition is met, where the default condition is that all provided tasks are done.

This means that we will have to enclose our single task in a list.

For example:

...
# wait for the task to be done
_ = await asyncio.wait()
# attempt to get a result from the task
value = task.result()

You can learn more about the asyncio.wait() function in the tutorial:

Now that we know how to avoid the InvalidStateError, let's look at some worked examples

Example of InvalidStateError: Result is not set

We can explore an example that raises an InvalidStateError.

In this case, we can define a custom coroutine that does some work and then returns a value. Our main program will schedule the coroutine as a background task, sleep a moment then attempt to retrieve and report the task result, which will fail with an InvalidStateError exception.

Firstly, we can define a custom coroutine that reports a message, sleeps for one second, and then returns a value.

The work() coroutine below implements this.

# custom coroutine
async def work():
    # report a message
    print('work() is working!')
    # sleep a moment to simulate work
    await asyncio.sleep(1)
    # return the result
    return 100

Next, we can define the main() coroutine.

A message is printed, then a work() coroutine is created and scheduled as a background task. The main() coroutine then sleeps for half a second, not long enough for the task to be completed.

It then retrieves and reports the result from the task before printing a final message.

The main() coroutine below implements this.

# main coroutine
async def main():
    # entry point of the program
    print('Main is running')
    # schedule as task
    task = asyncio.create_task(work())
    # wait a moment
    await asyncio.sleep(0.5)
    # report the result
    print(f'Got: {task.result()}')
    # report final message
    print('Main is done')

Finally, we can start the event loop.

...
# start the event loop
asyncio.run(main())

Tying this together, the complete example is listed below.

# SuperFastPython.com
# example of InvalidStateError: Result is not set
import asyncio

# custom coroutine
async def work():
    # report a message
    print('work() is working!')
    # sleep a moment to simulate work
    await asyncio.sleep(1)
    # return the result
    return 100

# main coroutine
async def main():
    # entry point of the program
    print('Main is running')
    # schedule as task
    task = asyncio.create_task(work())
    # wait a moment
    await asyncio.sleep(0.5)
    # report the result
    print(f'Got: {task.result()}')
    # report final message
    print('Main is done')

# start the event loop
asyncio.run(main())

Running the example first starts the asyncio event loop and runs the main() coroutine.

The main() coroutine runs and reports a message.

It then creates the work() coroutine and schedules it as a background task. It then suspends and sleeps for half a second.

The work() task runs and reports a message before suspending and sleeping for one second.

The main() coroutine resumes and attempts to retrieve the result from the task.

This fails with an InvalidStateError exception and the message "Result is not set".

This highlights that if we attempt to retrieve a result via the result() method before the task is done an InvalidStateError exception will be raised.

Main is running
work() is working!
Traceback (most recent call last):
  File "...", line 28, in <module>
    asyncio.run(main())
  File ".../asyncio/runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File ".../asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../asyncio/base_events.py", line 653, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "...", line 23, in main
    print(f'Got: {task.result()}')
                  ^^^^^^^^^^^^^
asyncio.exceptions.InvalidStateError: Result is not set.

Example of Avoiding InvalidStateError By Awaiting

We can explore how to avoid the InvalidStateError exception by awaiting the target task directly.

In this case, we can update the above example to await the task, and then retrieve the return value.

...
# wait for the task to be done
await task
# report the result
print(f'Got: {task.result()}')

The updated main() coroutine with this change is listed below.

# main coroutine
async def main():
    # entry point of the program
    print('Main is running')
    # schedule as task
    task = asyncio.create_task(work())
    # wait for the task to be done
    await task
    # report the result
    print(f'Got: {task.result()}')
    # report final message
    print('Main is done')

Tying this together, the complete example is listed below.

# SuperFastPython.com
# example of avoiding InvalidStateError by awaiting
import asyncio

# custom coroutine
async def work():
    # report a message
    print('work() is working!')
    # sleep a moment to simulate work
    await asyncio.sleep(1)
    # return the result
    return 100

# main coroutine
async def main():
    # entry point of the program
    print('Main is running')
    # schedule as task
    task = asyncio.create_task(work())
    # wait for the task to be done
    await task
    # report the result
    print(f'Got: {task.result()}')
    # report final message
    print('Main is done')

# start the event loop
asyncio.run(main())

Running the example first starts the asyncio event loop and runs the main() coroutine.

The main() coroutine runs and reports a message.

It then creates the work() coroutine and schedules it as a background task. It then suspends and awaits the task directly.

The work() task runs and reports a message before suspending and sleeping for one second.

The work() task then resumes and returns a value, terminating the task.

The main() coroutine resumes and retrieves the return value from the task and reports it before reporting a final message.

This highlights how we can avoid an InvalidStateError exception by awaiting the task directly.

Main is running
work() is working!
Got: 100
Main is done

Example of Avoiding InvalidStateError By asyncio.gather()

We can explore how to avoid an InvalidStateError exception by awaiting the task with asyncio.gather().

In this case, we can update the example by awaiting a call to asyncio.gather() and passing the task instance as an argument.

...
# wait for the task to be done
await asyncio.gather(task)

The updated main() coroutine with this change is listed below.

# main coroutine
async def main():
    # entry point of the program
    print('Main is running')
    # schedule as task
    task = asyncio.create_task(work())
    # wait for the task to be done
    await asyncio.gather(task)
    # report the result
    print(f'Got: {task.result()}')
    # report final message
    print('Main is done')

Tying this together, the complete example is listed below.

# SuperFastPython.com
# example of avoiding InvalidStateError by asyncio.gather()
import asyncio

# custom coroutine
async def work():
    # report a message
    print('work() is working!')
    # sleep a moment to simulate work
    await asyncio.sleep(1)
    # return the result
    return 100

# main coroutine
async def main():
    # entry point of the program
    print('Main is running')
    # schedule as task
    task = asyncio.create_task(work())
    # wait for the task to be done
    await asyncio.gather(task)
    # report the result
    print(f'Got: {task.result()}')
    # report final message
    print('Main is done')

# start the event loop
asyncio.run(main())

Running the example first starts the asyncio event loop and runs the main() coroutine.

The main() coroutine runs and reports a message.

It then creates the work() coroutine and schedules it as a background task.

It then suspends and awaits the task in a call to asyncio.gather().

The work() task runs and reports a message before suspending and sleeping for one second.

The work() task then resumes and returns a value, terminating the task.

The main() coroutine resumes and retrieves and reports the return value from the task.

This highlights how we can avoid an InvalidStateError exception by awaiting the task with asyncio.gather().

Main is running
work() is working!
Got: 100
Main is done

Example of Avoiding InvalidStateError By asyncio.wait()

We can explore how to avoid an InvalidStateError exception by awaiting the task with asyncio.wait().

In this case, we can update the example by awaiting a call to asyncio.wait() and passing the task instance as an argument wrapped in a new list object.

...
# wait for the task to be done
_ = await asyncio.wait()

The updated main() coroutine with this change is listed below.

# main coroutine
async def main():
    # entry point of the program
    print('Main is running')
    # schedule as task
    task = asyncio.create_task(work())
    # wait for the task to be done
    _ = await asyncio.wait()
    # report the result
    print(f'Got: {task.result()}')
    # report final message
    print('Main is done')

Tying this together, the complete example is listed below.

# SuperFastPython.com
# example of avoiding InvalidStateError by asyncio.wait()
import asyncio

# custom coroutine
async def work():
    # report a message
    print('work() is working!')
    # sleep a moment to simulate work
    await asyncio.sleep(1)
    # return the result
    return 100

# main coroutine
async def main():
    # entry point of the program
    print('Main is running')
    # schedule as task
    task = asyncio.create_task(work())
    # wait for the task to be done
    _ = await asyncio.wait()
    # report the result
    print(f'Got: {task.result()}')
    # report final message
    print('Main is done')

# start the event loop
asyncio.run(main())

Running the example first starts the asyncio event loop and runs the main() coroutine.

The main() coroutine runs and reports a message.

It then creates the work() coroutine and schedules it as a background task.

It then suspends and awaits the task in a call to asyncio.wait() as a one-item list.

The work() task runs and reports a message before suspending and sleeping for one second.

The work() task then resumes and returns a value, terminating the task.

The main() coroutine resumes and retrieves and reports the return value from the task.

This highlights how we can avoid an InvalidStateError exception by awaiting the task within asyncio.wait().

Main is running
work() is working!
Got: 100
Main is done

Takeaways

You now know why we get the InvalidStateError: "Result is not set" and how to fix it.