There are a series of common exceptions and warnings in asyncio, and we see most of them when we first get started developing asyncio programs.
Knowing about them and how to fix them is important.
In this tutorial, you will discover the 7 most common asyncio warnings and exceptions and how to avoid them.
Let’s get started.
Asyncio Common Exceptions and Warnings
The 7 most common asyncio warnings and exceptions are as follows:
- RuntimeWarning: Coroutine Was Never Awaited
- RuntimeWarning: Enable tracemalloc to get the object allocation traceback
- SyntaxError: ‘await’ outside function
- RuntimeError: cannot reuse already awaited coroutine
- InvalidStateError: Result is not set
- InvalidStateError: Exception is not set
- Warning: Asyncio Task exception was never retrieved
Let’s take a closer look at each in turn and how to avoid them.
Run loops using all CPUs, download your FREE book to learn how.
RuntimeWarning: Coroutine Was Never Awaited
It is common for Python developers to get a RuntimeWarning when getting started with asyncio.
The warning looks as follows:
- RuntimeWarning: coroutine ” was never awaited
Where the name of the coroutine function is between the quotes.
For example, if you tried to run a coroutine function with the name “custom_coro“, then the RuntimeWarning message would look as follows:
- RuntimeWarning: coroutine ‘custom_coro’ was never awaited
Don’t worry, you’re not alone.
Many developers get this warning.
Why are you getting this warning and how can we fix it?
If you try to run a coroutine and get a RuntimeWarning, the reason is that the coroutine was not executed.
For example, you may have defined a coroutine function as follows:
1 2 3 |
# define a custom coroutine async def custom_coro(): print('Hello there') |
You may then try to run the coroutine by calling the coroutine function.
For example:
1 2 3 |
... # call the coroutine function custom_coro() |
This will cause the error.
It looks like you are calling the function, but you are not.
The reason is that calling the coroutine function does not run the coroutine.
Instead, it creates a coroutine object.
For example:
1 2 3 |
... # create a coroutine object: coro = custom_coro() |
If this coroutine object is not given an opportunity to run, then Python will report the RuntimeWarning.
To run a coroutine object from a regular Python program, you must pass it to the asyncio.run() function.
This will start the asyncio event loop and execute your provided coroutine.
For example:
1 2 3 |
... # run the coroutine asyncio.run(coro) |
This is often performed on one line using a compound statement.
For example:
1 2 3 |
... # run the coroutine asyncio.run(custom_coro()) |
If you are trying to run a coroutine from within a coroutine, then this can be achieved using the “await” expression.
This will suspend the caller coroutine and run the called coroutine.
For example:
1 2 3 |
... # suspend and run the other coroutine await coro |
This too is typically performed on one line using a compound statement.
For example:
1 2 3 |
... # suspend and run the other coroutine await custom_coro() |
You can learn more about this RuntimeWarning and how to avoid it in the tutorial:
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
It is common to get a RuntimeWarning warning when developing asyncio programs.
This is especially common when you are first getting started. You may see RuntimeWarning messages reported in your standard output or standard error.
A common RuntimeWarning is as follows:
- RuntimeWarning: Enable tracemalloc to get the object allocation traceback
The “RuntimeWarning: Enable tracemalloc to get the object allocation traceback” is emitted by asyncio programs.
Most commonly it is emitted when an asyncio program creates a coroutine and does not await it.
This means that the coroutine is never run in the asyncio event loop.
Creating and not running a coroutine is a common error in an asyncio program and typically raises a RuntimeWarning with the message:
- RuntimeWarning: coroutine ‘…’ was never awaited
Where ‘…’ is replaced with the name of the coroutine that was not awaited.
Along with this warning, the following warning is emitted:
- RuntimeWarning: Enable tracemalloc to get the object allocation traceback
It is emitted because the Python interpreter is giving us advice on how to track down the cause of the previous RuntimeWarning.
It is suggesting that we use the tools in the tracemalloc module to find the coroutine that was never awaited.
The tracemalloc module provides tools for debugging Python programs.
Specifically, provides tools for tracing memory allocation in the programs.
The tracemalloc module is a debug tool to trace memory blocks allocated by Python.
— tracemalloc — Trace memory allocations
It is a reasonably sophisticated module and although we may use it directly, we are more likely to use it indirectly via a memory profiling tool applied to our program.
We could instrument our asyncio program with the tracemalloc module to discover the cause of the “RuntimeWarning: coroutine ‘…’ was never awaited” warning, but this is typically not required.
We can fix both RuntimeWarning warnings by awaiting the coroutine that was not awaited.
When the “RuntimeWarning: coroutine ‘…’ was never awaited” is emitted, it will report the file and line on which the coroutine was created that was never awaited.
We can go directly to this line and await the coroutine or schedule the coroutine in a way that is appropriate for our program.
For example, typically we might call a coroutine like a function, causing both RuntimeWarning warnings to be emitted:
1 2 3 |
... # call the work coroutine work() |
This will create a coroutine object, but not run it in the event loop.
It is equivalent to the following:
1 2 3 |
... # call the work coroutine coro = work() |
We can fix the RuntimeWarning warnings by explicitly awaiting the coroutine.
For example:
1 2 3 |
... # await the work coroutine await work() |
You can learn more about this RuntimeWarning and how to avoid it in the tutorial:
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.
SyntaxError: ‘await’ outside function
It is common to get a SyntaxError referring to “await” when getting started with asyncio.
Specifically, we may be developing an asyncio program and get the error:
- SyntaxError: ‘await’ outside function
The program does not even run.
Instead, the SyntaxError is reported by the Python interpreter before it even begins executing instructions.
Why are we getting this error?
Await refers to the “await” expression used with coroutines.
It can only be used within a coroutine and is used to yield execution to an awaitable.
Await expression: Suspend the execution of coroutine on an awaitable object. Can only be used inside a coroutine function.
— Python Expressions
An awaitable may be another coroutine or a coroutine wrapped in a Task object for independent execution.
An object that can be used in an await expression. Can be a coroutine or an object with an __await__() method.
— Python Glossary
Put another way, await will cause the caller coroutine to suspend execution at that point and wait for the given awaitable to be done.
The await expression can be used by using the “await” keyword followed by an awaitable.
For example:
1 2 3 |
... # await a coroutine await custom_coroutine() |
This line does a few things.
Firstly, it creates a coroutine object.
It then schedules the coroutine for execution in the asyncio event loop.
The caller then suspends execution and waits for the new coroutine to be done.
The awaitable coroutine may return a value that we may want after the awaitable is done.
This can be assigned as part of the await expression.
For example:
1 2 3 |
... # await a coroutine and store the return value value = await custom_coroutine() |
We will get a SyntaxError when we use an “await” expression outside of a coroutine.
For example:
- If we use await in a lambda.
- If we use await in a regular function.
- If we use await in an object method.
- If we use await in a module.
And so on.
We are probably trying to await a coroutine or a task within a function.
This is the most common case.
For example:
1 2 3 4 |
# our function def somefunction(): # await a coroutine await asyncio.sleep(1) |
This will result in a SyntaxError.
An await expression can only ever be used within a coroutine.
We can fix the SyntaxError by ensuring that all of our await expressions are within a coroutine.
Recall that we can define a coroutine using the “async def” expression.
For example:
1 2 3 4 |
# our function async def somefunction(): # await a coroutine await asyncio.sleep(1) |
This will not result in a SyntaxError.
This means that when developing our asyncio programs all of our functions and methods should be defined using an “async def” expression. They must be coroutines in order to use the await expression.
You can learn more about this SyntaxError and how to avoid it in the tutorial:
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
RuntimeError: cannot reuse already awaited coroutine
It is common to get a RuntimeError exception with the message:
- RuntimeError: cannot reuse already awaited coroutine
Developers often get this exception when first getting started with asyncio.
What does this RuntimeError exception mean?
The RuntimeError exception with the message “cannot reuse already awaited coroutine” is caused because we attempt to run the same coroutine object more than once.
Recall that when we call a coroutine, e.g. custom_coro(), that it creates a coroutine object.
We can then run the coroutine object by awaiting it or by scheduling it as a task.
For example:
1 2 3 4 5 |
... # create a coroutine coro = custom_coro() # run the coroutine await coro |
Or:
1 2 3 4 5 |
... # create a coroutine coro = custom_coro() # schedule coroutine for execution later asyncio.create_task(coro) |
If we attempt to run the same coroutine object more than once it will raise an exception:
- RuntimeError: cannot reuse already awaited coroutine
For example:
1 2 3 4 5 6 7 |
... # create a coroutine coro = custom_coro() # run coroutine await coro # run coroutine again await coro |
Or:
1 2 3 4 5 6 7 |
... # create a coroutine coro = custom_coro() # schedule coroutine for execution later asyncio.create_task(coro) # schedule coroutine again for execution later asyncio.create_task(coro) |
We cannot execute the same coroutine object more than once.
We can avoid the RuntimeError by creating coroutines and running them directly in one line.
For example:
1 2 3 |
... # create and await coroutine await custom_coro() |
Or
1 2 3 |
... # create and schedule coroutine for execution later asyncio.create_task(custom_coro()) |
You can learn more about this RuntimeError and how to avoid it in the tutorial:
InvalidStateError: Result is not set
It is common to get an InvalidStateError exception when using asyncio.
An InvalidStateError exception is raised with the message:
- InvalidStateError: Result is not set
Developers often get this exception when first getting started with asyncio.
What does this InvalidStateError exception mean?
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:
1 2 3 |
... # 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 fails with an uncancelled exception.
The problem is, that 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.
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.
The simplest approach is to await the task directly.
For example:
1 2 3 |
... # wait for the task to be done and get the result result = await task |
This does two things:
- Waits for the task to be done.
- 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:
1 2 3 4 5 |
... # 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 this InvalidStateError and how to avoid it in the tutorial:
InvalidStateError: Exception is not set
It is common to get an InvalidStateError exception when using asyncio.
An InvalidStateError exception is raised with the message:
- InvalidStateError: Exception is not set
Developers often get this exception when first getting started with asyncio.
What does this InvalidStateError exception mean?
We get an InvalidStateError exception when we try to retrieve an exception from an asyncio.Task while the task is still running.
This will happen if we call the exception() method on an asyncio.Task instance and the task is not done.
For example:
1 2 3 |
... # attempt to get an exception from the task exc = task.exception() |
This is the regular way to get an unhandled exception from a task.
It will return None if the task is completed successfully or an exception instance if an exception was raised in the task.
The problem is, we must wait until the task has been completed, e.g., has the state “done“. This means that the done() method on the task returns True.
We can avoid an InvalidStateError when calling the exception() method by waiting for the task to be done.
There are many ways we can do this.
The simplest approach might be to attempt to await the task directly.
For example:
1 2 3 4 5 |
... # wait for the task to be done await task # attempt to get an exception from the task exc = task.exception() |
This is not helpful because if the task fails with an unhandled exception, it will be propagated to the caller and re-raised.
Therefore we would need to wrap the await in a try-except structure.
For example:
1 2 3 4 5 |
try: # wait for the task to be done await task except Exception as e: ... |
We would no longer need to call the exception() method on the task.
You can learn more about this InvalidStateError and how to avoid it in the tutorial:
Warning: Asyncio Task exception was never retrieved
A never-retrieved exception is an exception raised in a coroutine or task that is not explicitly retrieved.
If a Future.set_exception() is called but the Future object is never awaited on, the exception would never be propagated to the user code. In this case, asyncio would emit a log message when the Future object is garbage collected.
— Developing with asyncio
If an exception is not retrieved, then a warning message is reported automatically by the asyncio event loop with the message:
- Task exception was never retrieved
This message is reported along with the details of the task for each never-retrieved exception.
The message is not reported until the event loop is terminated, e.g. the program has ended.
We can avoid this message in our programs by retrieving the exception.
An exception can be retrieved from an asyncio task in one of three ways:
- Awaiting the coroutine or task object, the exception is re-raised.
- Calling the result() method to get the return value, the exception is re-raised
- Calling the exception() method to get the exception.
For example:
1 2 3 4 5 6 |
# retrieve the exception from the task exc = task.exception() # check if the exception is present if exc: # report the exception print(exc) |
You can learn more about this warning in the tutorial:
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 about the 7 most common asyncio warnings and exceptions and how to avoid them.
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 Paul Volkmer on Unsplash
Do you have any questions?