asyncio.Runner() context manager is a new feature in Python 3.11 that simplifies running multiple async functions in the same context. It takes care of creating and closing an asyncio event loop, as well as managing the
contextvars.Context for the async functions.
To use it, you need to create an instance of
asyncio.Runner() with the
with statement, and then call its
run() method with the async function you want to execute. You can also pass a custom context to the
run() method if you want to use a different
contextvars.Context for the async function. The runner will return the result or raise the exception of the async function.
Here is an example of using
asyncio.Runner() to run two async functions that print some messages with delays:
import asyncio async def first(): await asyncio.sleep(1) print("Welcome to Sling Academy!") async def second(): await asyncio.sleep(2) print("Have a nice day and have fun with Python!") with asyncio.Runner() as runner: runner.run(first()) runner.run(second())
This will output two messages (the second one will appear two seconds after the first one):
Welcome to Sling Academy! Have a nice day and have fun with Python!
close() method of
asyncio.Runner() is used to close the runner and release its resources. It will finalize any asynchronous generators, shut down the default executor, close the event loop, and release the embedded contextvars.Context. You should call this method when you are done using the runner or use the
with statement (like the example above) to automatically close it at the end of the block.
The example in the preceding section is basic, and you can get bored with it. Let’s examine a little bit more advanced program.
This example creates two coroutines that perform different tasks: one prints the current time every second for 10 seconds, and the other prints a message after 5 seconds. It then uses an asyncio runner to run both coroutines in the same event loop, one after the other. This demonstrates how to use the asyncio runner to execute multiple coroutines without creating a wrapper coroutine or multiple event loops.
import asyncio import time # A coroutine that prints the current time async def print_time(): print(time.strftime("%X")) # A coroutine that waits for a given number of seconds async def wait(seconds): await asyncio.sleep(seconds) # A coroutine that prints the current time every second for 10 seconds async def print_time_every_second(): for _ in range(10): await print_time() await wait(1) # A coroutine that prints a message after 5 seconds async def print_message(): await wait(5) print("Welcome to Sling Academy!") # Create an asyncio runner with asyncio.Runner() as runner: # Run the first coroutine runner.run(print_time_every_second()) # Run the second coroutine runner.run(print_message())
Output (it depends on when you run the code):
22:34:23 22:34:24 22:34:25 22:34:26 22:34:27 22:34:28 22:34:29 22:34:30 22:34:31 22:34:32 Welcome to Sling Academy!
This example uses the context manager interface of the
asyncio.Runner() class, which will close the event loop for us when we’re finished with it. It also uses two coroutines:
print_message(), which are executed in the same event loop by calling the
run() method of the runner.
The difference between asyncio.Runner() and asyncio.run()
asyncio.Runner() context manager is different from asyncio.run() function in several ways. The asyncio.run() function always creates a new event loop and closes it at the end, and it can only run one async function at a time. It is meant to be used as a main entry point for asyncio programs and, ideally, only be called once.
asyncio.Runner() context manager allows you to reuse the same event loop and context for multiple async functions, and it also lets you pass a custom context to each async function if you want. It is meant to be used for scenarios where you need to run several top-level async functions in the same context, and it can be nested inside other async functions or contexts.