A coroutine cannot be run directly outside of a Python program.
Attempting to “call” a coroutine will result in RuntimeWarning messages. Attempting to await a coroutine from a Python program results in a SyntaxError error.
In this tutorial, you will discover how to run a one-off coroutine outside an asyncio program.
Let’s get started.
Need to Run Coroutine Outside Of Asyncio
Sometimes we need to execute an asyncio coroutine outside of an asyncio program.
This may be for many reasons, for example:
- Asynchronous Operations: When you have a specific task that requires asynchrony, such as making an asynchronous HTTP request or handling a non-blocking I/O operation, you can use a one-off coroutine to accomplish that task without making the entire program asynchronous.
- Third-Party Libraries: When integrating third-party libraries or modules that expose asynchronous interfaces, you can use one-off coroutines to interact with those libraries even if the rest of your program is synchronous.
- Testing: When writing unit tests, you might need to simulate asynchronous behavior for specific functions or methods without transitioning the entire testing environment to asynchronous mode.
In these cases, we don’t want to convert our Python program into an asynchronous asyncio program.
Instead, we want to issue a one-off asynchronous call. We want to execute a coroutine and get a result.
Put another way, how can we await outside of an asyncio program?
Run loops using all CPUs, download your FREE book to learn how.
We Cannot Run a Coroutine Directly From Python.
There is no direct way to await a coroutine (async function) outside of an asyncio event loop.
- We cannot execute a coroutine like a function and get a result.
- We cannot use “await” outside of a coroutine.
Let’s make this clear with some examples.
We Cannot Run Coroutine Directly From Python
We cannot “call” a coroutine directly from a Python program.
For example, consider a simple coroutine that reports a message and sleeps a moment.
1 2 3 4 5 6 7 8 |
# example of coroutine async def work(): # report a message print('Coroutine has started') # simulate awaiting something await asyncio.sleep(1) # report a message print('Coroutine is done') |
We cannot call this coroutine from Python.
For example:
1 2 3 |
... # run a coroutine work() |
This does not “call” the coroutine. Instead, it creates a coroutine object that is not assigned.
The coroutine will not be executed and will result in a RuntimeWarning.
Tying this together, 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 |
# SuperFastPython.com # example of attempting to run a coroutine directly, results in error import asyncio # example of coroutine async def work(): # report a message print('Coroutine has started') # simulate awaiting something await asyncio.sleep(1) # report a message print('Coroutine is done') # entry point of the program print('Main is running') # run a coroutine work() # report final message print('Main is done') |
Running the example reports a message.
It then “calls” the work() coroutine.
This fails. Instead, it creates a coroutine object that is not assigned.
The coroutine is not run, resulting in two RuntimeWarning messages:
- RuntimeWarning: coroutine ‘work’ was never awaited
- RuntimeWarning: Enable tracemalloc to get the object allocation traceback
The first warning highlights that we did not achieve the desired effect. The coroutine object was created and not run.
You can learn more about this RuntimeWarning in the tutorial:
The second warning gives a suggestion on how to track down the cause of the first warning, e.g. where a coroutine was created and not run.
This advice would be helpful in a much larger program, but not in this case.
You can learn more about this second RuntimeWarning in the tutorial:
1 2 3 4 5 |
Main is running ...:17: RuntimeWarning: coroutine 'work' was never awaited work() RuntimeWarning: Enable tracemalloc to get the object allocation traceback Main is done |
What do we mean that work() created a coroutine object?
To make this clearer, we can rewrite the “call” to work() to highlight what really happened:
1 2 3 4 5 |
... # create a coro object coro = work() # report what we have print(type(coro)) |
If you are new to coroutine objects, you can learn more in the tutorial:
Tying this together, 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 |
# SuperFastPython.com # example of attempting to run a coroutine directly, results in error import asyncio # example of coroutine async def work(): # report a message print('Coroutine has started') # simulate awaiting something await asyncio.sleep(1) # report a message print('Coroutine is done') # entry point of the program print('Main is running') # create a coro object coro = work() # report what we have print(type(coro)) # report final message print('Main is done') |
Running the example, again we can see that the coroutine was not run.
Instead, we can see that we created a “coroutine” object.
Reporting the type shows the class type of “coroutine“.
This coroutine object must be executed using an asyncio event loop.
1 2 3 4 |
Main is running <class 'coroutine'> Main is done sys:1: RuntimeWarning: coroutine 'work' was never awaited |
To execute a coroutine object, we must await it.
Next, let’s look at what happens if we try to await the coroutine object directly.
Cannot await Outside of Coroutine
We now know that we can create a coroutine object when “calling” a coroutine.
The problem is we cannot await the coroutine object directly outside of an asyncio program.
For example:
1 2 3 |
... # run a coroutine await work() |
This will fail because we cannot use the “await” expression outside of a coroutine.
It results in a syntax error.
You can learn more about the await expression in the tutorial:
Tying this together, 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 |
# SuperFastPython.com # example of attempting to await a coroutine outside of asyncio import asyncio # example of coroutine async def work(): # report a message print('Coroutine has started') # simulate awaiting something await asyncio.sleep(1) # report a message print('Coroutine is done') # entry point of the program print('Main is running') # run a coroutine await work() # report final message print('Main is done') |
The program cannot be executed by the Python interpreter.
Using the “await” expression outside of a coroutine is a SyntaxError.
1 2 3 4 |
File "...", line 17 await work() ^^^^^^^^^^^^ SyntaxError: 'await' outside function |
We can only execute the await expression within a coroutine.
Seems like we are stuck in some circular reasoning.
- We cannot execute a coroutine, it must be awaited.
- We cannot use await outside of a coroutine.
Then how can we run a one-off coroutine?
How to Run Coroutine Outside of Asyncio
We don’t have to convert our normal Python program into an asyncio program.
Instead, the solution is to briefly start a small asyncio event loop and execute a one-off coroutine.
There are two ways that we can execute a coroutine from our normal non-asyncio Python program, they are:
- Use asyncio.run()
- Use asyncio.Runner()
Run One-Off Coroutine with asyncio.run()
We can execute a one-off coroutine from our Python program using the asyncio.run() function.
This involves calling the asyncio.run() function and passing it the coroutine object to execute.
For example:
1 2 3 |
... # run a one-off coroutine asyncio.run(coro()) |
We can define a helper function to do this for us.
The helper function takes the name of the coroutine to execute and any arguments to the coroutine.
1 2 3 4 |
# helper function to run a coroutine def run_coroutine_helper(coro_name, *args, **kwargs): # start the event loop to run one coroutine return asyncio.run(coro_name(*args, **kwargs)) |
We can then just call our helper function to have a one-off coroutine executed.
1 2 3 |
... # run a coroutine result = run_coroutine_helper(work, arg1, arg2) |
You can learn more about executing coroutines in the tutorial:
Run One-Off Coroutine with asyncio.Runner()
We can also execute one-off coroutines using the asyncio.Runner() context manager.
The asyncio.Runner() context manager is then created with a named variable, then the run() method can be used to create and run many coroutines as we require.
For example:
1 2 3 4 5 |
... # start the event loop to run one coroutine with asyncio.Runner() as runner: runner.run(work1()) runner.run(work2()) |
If we have a single coroutine to execute, we can create a helper function to use the asyncio.Runner() context manager for us.
For example:
1 2 3 4 5 |
# helper function to run a coroutine def run_coroutine_helper(coro_name, *args, **kwargs): # start the event loop to run one coroutine with asyncio.Runner() as runner: return runner.run(coro_name(*args, **kwargs)) |
We can then just call our helper function to have a one-off coroutine executed.
1 2 3 |
... # run a coroutine result = run_coroutine_helper(work, arg1, arg2) |
You can learn more about how to use the asyncio.Runner() context manager in the tutorial:
Now that we know how to execute one-off coroutines, 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 Running a One-Off Coroutine with asyncio.run()
We can explore an example of executing a one-off coroutine with asyncio.run().
In this example, we will first define a coroutine that reports a message, awaits another coroutine, and reports a final message. We will use our custom helper function to execute the coroutine via the asyncio.run() function.
Firstly, we can define the coroutine we want to run.
1 2 3 4 5 6 7 8 |
# example of coroutine async def work(): # report a message print('Coroutine has started') # simulate awaiting something await asyncio.sleep(1) # report a message print('Coroutine is done') |
Next, we will use the helper function that will start the asyncio event loop and create and then execute a given coroutine.
1 2 3 4 |
# helper function to run a coroutine def run_coroutine_helper(coro_name, *args, **kwargs): # start the event loop to run one coroutine return asyncio.run(coro_name(*args, **kwargs)) |
Finally, the entry point of the program reports a message, calls our helper to execute the target coroutine, and then reports a final message.
1 2 3 4 5 6 |
# entry point of the program print('Main is running') # run a coroutine run_coroutine_helper(work) # report final message print('Main is done') |
Tying this together, 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 |
# SuperFastPython.com # example of running a coroutine outside of asyncio with asyncio.run import asyncio # example of coroutine async def work(): # report a message print('Coroutine has started') # simulate awaiting something await asyncio.sleep(1) # report a message print('Coroutine is done') # helper function to run a coroutine def run_coroutine_helper(coro_name, *args, **kwargs): # start the event loop to run one coroutine return asyncio.run(coro_name(*args, **kwargs)) # entry point of the program print('Main is running') # run a coroutine run_coroutine_helper(work) # report final message print('Main is done') |
Running the example first reports the start message.
It then calls our helper function.
The helper function runs and starts the asyncio event loop via asyncio.run(). It creates the work() coroutine object with no arguments and passes it to the asyncio.run() function.
The asyncio event loop runs and executes the work() coroutine.
The work() coroutine runs and prints a message then awaits the asyncio.sleep() coroutine for one second. It resumes and reports a final message before terminating.
The asyncio event loop then terminates.
The run_coroutine_helper() function returns and the main thread prints a final message.
This highlights how we can execute a one-off coroutine in our Python program using the asyncio.run() function.
1 2 3 4 |
Main is running Coroutine has started Coroutine is done Main is done |
Next, let’s look at how we can execute a one-off coroutine using the asyncio.Runner context manager.
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
Example of Running a One-Off Coroutine with asyncio.Runner
We can explore an example of executing a one-off coroutine using the asyncio.Runner context manager.
In this case, we can update the above example to use a different run_coroutine_helper() function that uses the asyncio.Runner context manager to create and run a given coroutine.
1 2 3 4 5 |
# helper function to run a coroutine def run_coroutine_helper(coro_name, *args, **kwargs): # start the event loop to run one coroutine with asyncio.Runner() as runner: return runner.run(coro_name(*args, **kwargs)) |
And that’s it.
Tying this together, 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 |
# SuperFastPython.com # example of running a coroutine outside of asyncio with asyncio.Runner import asyncio # example of coroutine async def work(): # report a message print('Coroutine has started') # simulate awaiting something await asyncio.sleep(1) # report a message print('Coroutine is done') # helper function to run a coroutine def run_coroutine_helper(coro_name, *args, **kwargs): # start the event loop to run one coroutine with asyncio.Runner() as runner: return runner.run(coro_name(*args, **kwargs)) # entry point of the program print('Main is running') # run a coroutine run_coroutine_helper(work) # report final message print('Main is done') |
Running the example first reports the start message.
It then calls our helper function.
The helper function runs and starts the asyncio event loop via asyncio.Runner context manager. It creates the work() coroutine object with no arguments and passes it to the run() method.
The asyncio event loop runs and executes the work() coroutine.
The work() coroutine runs and prints a message then awaits the asyncio.sleep() coroutine for one second. It resumes and reports a final message before terminating.
The asyncio.Runner context manager exits which closes the asyncio event loop.
The run_coroutine_helper() function returns and the main thread prints a final message.
This highlights how we can execute a one-off coroutine in our Python program using the asyncio.Runner context manager.
1 2 3 4 |
Main is running Coroutine has started Coroutine is done Main is done |
Next, let’s look at how we can execute a one-off coroutine that takes an argument and returns a value.
Example of Running a One-Off Coroutine with Argument and Return Value
We can explore an example of executing a one-off coroutine that takes an argument and returns a value.
This is a more common use case as we typically need to execute a one-off coroutine in order to either transmit data to a web server or retrieve data from a web server.
In this case, we will update the work() coroutine to take a number argument and return the provided number argument multiplied by 100.
The updated work() coroutine with this change is listed below.
1 2 3 4 5 6 7 8 9 10 |
# example of coroutine async def work(arg): # report a message print('Coroutine has started') # simulate awaiting something await asyncio.sleep(1) # report a message print('Coroutine is done') # return a value return arg * 100 |
We will execute this one-off coroutine using the asyncio.run() function via the helper function we developed above.
1 2 3 4 |
# helper function to run a coroutine def run_coroutine_helper(coro_name, *args, **kwargs): # start the event loop to run one coroutine return asyncio.run(coro_name(*args, **kwargs)) |
Finally, in the main thread, we can call the helper function and specify the name of the work() coroutine and the argument to the coroutine. We can then retrieve and report the return value.
1 2 3 4 5 |
... # run a coroutine result = run_coroutine_helper(work, 100) # report the result print(f'Main got result: {result}') |
Tying this together, 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 running a one-off coroutine with argument and return value import asyncio # example of coroutine async def work(arg): # report a message print('Coroutine has started') # simulate awaiting something await asyncio.sleep(1) # report a message print('Coroutine is done') # return a value return arg * 100 # helper function to run a coroutine def run_coroutine_helper(coro_name, *args, **kwargs): # start the event loop to run one coroutine return asyncio.run(coro_name(*args, **kwargs)) # entry point of the program print('Main is running') # run a coroutine result = run_coroutine_helper(work, 100) # report the result print(f'Main got result: {result}') # report final message print('Main is done') |
Running the example first reports the start message.
It then calls our helper function with the name of the target coroutine to run and an argument.
The helper function runs and starts the asyncio event loop via asyncio.run(). It creates the work() coroutine object with a single argument and passes the coroutine object to the asyncio.run() function.
The asyncio event loop runs and executes the work() coroutine.
The work() coroutine runs and prints a message then awaits the asyncio.sleep() coroutine for one second. It resumes and reports a final message before returning the provided argument multiplied by 100.
The asyncio event loop then terminates.
The run_coroutine_helper() function returns the return value from the target coroutine.
The main thread reports the value from the coroutine returned via the helper function, and then reports a final message before terminating.
This highlights how we can execute a one-off coroutine that takes an argument and returns a value.
1 2 3 4 5 |
Main is running Coroutine has started Coroutine is done Main got result: 10000 Main is done |
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 run a one-off coroutine outside an asyncio program.
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 Tower Electric Bikes on Unsplash
Do you have any questions?