Normally, the asyncio.run()
function is used to execute coroutines. The function provides a higher level way of executing a one-off coroutines. Typically, the function performs several low-level operation in the background. The function creates a new event loop, schedules the coroutine in the loop, and after the coroutine is done executing it closes the loop.
import asyncio
async def task():
await asyncio.sleep(0.01) #simulate a delay
print('Hello, World!')
asyncio.run(task())
In the above example, we created a coroutine function i.e task()
, then used the asyncio.run()
function to execute the coroutine.
To run multiple coroutines, the asyncio.run()
function may be called more than once. However, it is worth noting that with each call, the process of creating a new event loop, scheduling the task and closing the loop will be repeated for each coroutine.
import asyncio
async def task1():
await asyncio.sleep(0.01)
print('Hello, World!')
async def task2():
await asyncio.sleep(0.01)
print('Goodbye, World!')
asyncio.run(task1())
asyncio.run(task2())
In the above example, we called the asyncio.run
twice, thus two independent event loops are used for each call.
The asyncio.Runner
class allows multiple tasks to be executed in the same event loop. Essentially, it allows an event loop to be re-used in execution of coroutines.
How asyncio.Runner works
The asyncio.Runner class allows multiple coroutines to be executed in the same event loop. The class was introduced in Python 3.11, in older versions we have to use the asyncio.run() function or other lower level approaches.
Instead of creating a new loop for each coroutine, asyncio.Runner uses a single event loop with all the coroutines.
The run()
method of the Runner
objects is used to execute the coroutines with the same interface as the asyncio.run()
function.
The following example shows how to run a coroutine with asyncio.Runner
.
import asyncio
import asyncio
async def task():
await asyncio.sleep(0.01)
print('Hello, World!')
runner = asyncio.Runner()
runner.run(task())
runner.close() #close the runner
In the above example we created a new Runner object i.e runner = asyncio.Runner()
. We then used the run()
method of the runner
object to execute the task()
coroutine, i.e runner.run(task())
.
The last line i.e runner.close()
is important, it tells the runner
to close the event loop since we are done running the coroutines. Runner
objects have a context manager interface, we can therefore avoid manually calling the runner.close()
method by using it in a with
statement. This way, the method is called automatically before the execution leaves the with block. The previous example will look as follows
import asyncio
import asyncio
async def task():
await asyncio.sleep(0.01)
print('Hello, World!')
with asyncio.Runner() as runner:
runner.run(task())
#the runner is closed
In the above example, the runner
gets automatically closed before the execution leaves the with
block, and there is therefore no need to call the runner.close()
method.
To execute multiple coroutines, just call the runner.run()
method multiple times with each coroutines.
import asyncio
import asyncio
async def task1():
await asyncio.sleep(0.01)
print('Hello, World!')
async def task2():
await asyncio.sleep(0.01)
print('Goodbye, World!')
with asyncio.Runner() as runner:
runner.run(task1())
runner.run(task2())
In the above example, we executed two coroutines i.e task1()
and task2()
in the same event loop.
Enabling debug mode
If the debug
parameter is set to True, the coroutines will be executed in debug mode.
import asyncio
async def task1():
await asyncio.sleep(0.01)
print('Hello, World!')
async def task2():
await asyncio.sleep(0.01)
print('Hello, World!')
with asyncio.Runner(debug = True) as runner:
runner.run(task1())
runner.run(task2())
Alternative approach - using awaits
An alternative approach for executing multiple tasks in the same event loop, is to create an extra coroutine, await the multiple coroutines in it, then run the extra coroutine using the asyncio.run()
function. This can especially be useful when running an older Python version i.e below Python 3.11.
import asyncio
async def task1():
await asyncio.sleep(0.01)
print('Hello, World!')
async def task2():
await asyncio.sleep(0.01)
print('Hello, World!')
#the extra coroutine
async def main():
await task1()
await task2()
asyncio.run(main()) #run main with asyncio.run
In the above example, we created a third coroutine i.e main()
then awaited the other coroutines in it. The same results are achieved as using asyncio.Runne
r, this is because normally, the await
expression ensures that the awaited coroutine is fully executed before moving on to the next.