The heart of asyncio programs is the event loop.
In this tutorial, you will discover how to use the asyncio event loop in Python.
Let’s get started.
What is the Asyncio Event Loop
Asyncio refers to the “asyncio” module and changes to the Python languages to support first-class coroutines.
It is an environment for executing coroutines in a single thread, called the event loop.
asyncio is a library to execute these coroutines in an asynchronous fashion using a concurrency model known as a single-threaded event loop.
— Page 3, Python Concurrency with asyncio, 2022.
The event loop is the core of an asyncio program.
It does many things, such as:
- Execute coroutines.
- Execute callbacks.
- Perform network input/output.
- Run subprocesses.
The event loop is the core of every asyncio application. Event loops run asynchronous tasks and callbacks, perform network IO operations, and run subprocesses.
— Asyncio Event Loop
Event loops are a common design pattern and became very popular in recent times given their use in JavaScript.
JavaScript has a runtime model based on an event loop, which is responsible for executing the code, collecting and processing events, and executing queued sub-tasks. This model is quite different from models in other languages like C and Java.
— The event loop, Mozilla.
The event loop, as its name suggests, is a loop. It manages a list of tasks (coroutines) and attempts to progress each in sequence in each iteration of the loop, as well as perform other tasks like executing callbacks and handling I/O.
The “asyncio” module provides functions for accessing and interacting with the event loop.
This is not required for typical application development.
Instead, access to the event loop is provided for framework developers, those that want to build on top of the asyncio module or enable asyncio for their library.
Application developers should typically use the high-level asyncio functions, such as asyncio.run(), and should rarely need to reference the loop object or call its methods.
— Asyncio Event Loop
Now that we know what the event loop is, let’s look at how we might interact with it.
Run loops using all CPUs, download your FREE book to learn how.
How To Start and Get An Event Loop
The typical way we create an event loop in asyncio applications is via the asyncio.run() function.
This function always creates a new event loop and closes it at the end. It should be used as a main entry point for asyncio programs, and should ideally only be called once.
— Asyncio Coroutines and Tasks
The function takes a coroutine and will execute it to completion.
We typically pass it our main coroutine and run our program from there.
You can learn more about running a coroutine program in the tutorial:
There are low-level functions for creating and accessing the event loop.
The asyncio.new_event_loop() function will create a new event loop and return access to it.
Create and return a new event loop object.
— Asyncio Event Loop
For example:
1 2 3 |
... # create and access a new asyncio event loop loop = asyncio.new_event_loop() |
We can demonstrate this with a worked example.
In the example below we will create a new event loop and then report its details.
1 2 3 4 5 6 7 8 |
# SuperFastPython.com # example of creating an event loop import asyncio # create and access a new asyncio event loop loop = asyncio.new_event_loop() # report defaults of the loop print(loop) |
Running the example creates the event loop, then reports the details of the object.
We can see that in this case the event loop has the type _UnixSelectorEventLoop and is not running, but is also not closed.
1 |
<_UnixSelectorEventLoop running=False closed=False debug=False> |
If an asyncio event loop is already running, we can get access to it via the asyncio.get_running_loop() function.
Return the running event loop in the current OS thread. If there is no running event loop a RuntimeError is raised. This function can only be called from a coroutine or a callback.
— Asyncio Event Loop
For example:
1 2 3 |
... # access he running event loop loop = asyncio.get_running_loop() |
There is also a function for getting or starting the event loop called asyncio.get_event_loop() function, but it was deprecated in Python 3.10 and should not be used.
What is an Event Loop Object
An event loop is implemented as a Python object.
The event loop object defines how the event loop is implemented and provides a common API for interacting with the loop, defined on the AbstractEventLoop class.
There are different implementations of the event loop for different platforms.
For example, Windows and Unix-based operations systems will implement the event loop in different ways, given the different underlying ways that non-blocking I/O is implemented on these platforms.
The SelectorEventLoop type event loop is the default on Unix-based operating systems like Linux and macOS.
The ProactorEventLoop type event loop is the default on Windows.
Third-party libraries may implement their own event loops to optimize for specific features.
Why Get Access to The Event Loop
Why would we want access to an event loop outside of an asyncio program?
There are many reasons why we may want access to the event loop from outside of a running asyncio program.
For example:
- To monitor the progress of tasks.
- To issue and get results from tasks.
- To fire and forget one-off tasks.
An asyncio event loop can be used in a program as an alternative to a thread pool for coroutine-based tasks.
An event loop may also be embedded within a normal asyncio program and accessed as needed.
Now that we know how to create or get the event loop, let’s look at some useful methods we might call to interact with it.
How To Run A Coroutine or Task in the Event Loop
Once we have access to the event loop, we can use it in different ways.
The most common usage of the event loop is to use it to execute a task.
We can call the run_until_complete() method on the event loop object to execute a task, future, or coroutine.
It will execute the provided task and block until it is complete.
Run until the future (an instance of Future) has completed. If the argument is a coroutine object it is implicitly scheduled to run as a asyncio.Task.
— Asyncio Event Loop
For example:
1 2 3 |
... # execute a task loop.run_until_complete(task) |
We can demonstrate this with a worked example.
The example below creates a new event loop, then defines a sleep coroutine and executes it to completion.
1 2 3 4 5 6 7 8 9 10 11 12 |
# SuperFastPython.com # example of running a task in a new event loop import asyncio # create and access a new asyncio event loop loop = asyncio.new_event_loop() # define a task coro = asyncio.sleep(2) # execute a task loop.run_until_complete(coro) # report a message print('done') |
Running the example first creates the event loop.
It then creates a sleep coroutine. The coroutine is then executed in the event loop that blocks until the task is complete.
Finally, a message is reported.
1 |
done |
We can schedule a task in the event loop via the create_task() method.
It takes a coroutine that is wrapped in a task. This method will return an asyncio.Task object and will execute the task as soon as it is able.
For example:
1 2 3 |
... # schedule a task for execution task = loop.create_task(task) |
We may have to wait for the task to complete if there is nothing else in the program.
This is because if the program exits while the event loop is running, a RuntimeWarning will be raised.
We can demonstrate this with an example.
The example below creates an event loop and then schedules a task. The program then exits before the task has had an opportunity to execute in the event loop.
1 2 3 4 5 6 7 8 9 10 11 12 |
# SuperFastPython.com # example of scheduling a task in a new event loop import asyncio # create and access a new asyncio event loop loop = asyncio.new_event_loop() # define a task coro = asyncio.sleep(2) # schedule a task task = loop.create_task(coro) # report a message print('done') |
Running the example creates the event loop, schedules the task, and reports a message.
The program exits before the event loop can execute and a RuntimeWarning is reported.
1 2 3 4 |
done Task was destroyed but it is pending! task: <Task pending name='Task-1' coro=<sleep() running at /...>> ...: RuntimeWarning: coroutine 'sleep' was never awaited |
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.
How to Shutdown the Event Loop
Once we are finished using the event loop, we can shut it down.
If there are no tasks running in the event loop, it can be closed by calling the close() method.
For example:
1 2 3 |
... # close the event loop loop.close() |
If we try to run a task on an event loop that has been closed, then an exception will be raised.
We can demonstrate this with an example.
In the example, we start the event loop, then close it, then after it is closed we attempt to run a task.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# SuperFastPython.com # example of running a task on a closed event loop import asyncio # create and access a new asyncio event loop loop = asyncio.new_event_loop() # report a message print('event loop started') # close the event loop loop.close() # report a message print('event loop closed') # attempt to run a task loop.run_until_complete(asyncio.sleep(0.5)) |
Running the example starts the event loop.
A message is reported then the event loop is closed.
We then attempt to run a task in the closed event loop and a RuntimeError is raised.
1 2 3 4 5 |
event loop started event loop closed Traceback (most recent call last): ... RuntimeError: Event loop is closed |
We can also shut down the event loop via the stop() method.
This will add a task to the event loop that will cause it to close.
For example:
1 2 3 |
... # stop the event loop loop.stop() |
We can also choose to run the event loop forever.
This can be achieved using the run_forever() method.
It configures the event loop to run until it is explicitly stopped via the stop() method, rather than once the main coroutine terminates.
Run the event loop until stop() is called.
— Asyncio Event Loop
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
How to Check The Status of the Event Loop
We can check the status of the event loop while it is running.
Firstly, we can check if the loop is running via the is_running() method.
For example:
1 2 3 4 |
... # check if the event loop is running if loop.is_running(): # ... |
We can also check if the event loop has closed via the is_closed() method.
For example:
1 2 3 4 |
... # check if the event loop is closed if loop.is_closed(): # ... |
We can explore this with a worked example.
The example below will create a new event loop, run a task, and close the loop. The running and closed status of the loop will be reported after each action.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# SuperFastPython.com # example of checking the status of the event loop import asyncio # create and access a new asyncio event loop loop = asyncio.new_event_loop() # check status print(f'running={loop.is_running()}, closed={loop.is_closed()}') # run a task loop.run_until_complete(asyncio.sleep(0.5)) # check status print(f'running={loop.is_running()}, closed={loop.is_closed()}') # close the event loop loop.close() # check status print(f'running={loop.is_running()}, closed={loop.is_closed()}') |
Running the example first creates the event loop and reports its status.
We can see that after being created that it is not running and not closed.
A task is run until completion and the status is reported again.
We can see that the status is unchanged. The loop is not running because the task is completed and there are no running tasks.
Finally, the loop is closed and we can see that finally, it has the closed status.
1 2 3 |
running=False, closed=False running=False, closed=False running=False, closed=True |
How to Run Tasks in Executors
The event loop provides a way to execute blocking tasks using threads or processes.
This can be achieved via the run_in_executor() method.
The method takes an Executor and functions with arguments to execute.
An Executor is either a ThreadPoolExecutor for a thread pool or a ProcessPoolExecutor for a process pool. An executor can be created and used in many default calls to the run_in_executor() method.
For example:
1 2 3 4 5 |
... # create an executor with ThreadPoolExecutor() as exe: # execute a function in event loop using executor loop.run_in_executor(exe, task) |
If the executor is not provided, e.g. a None is given, then the default executor is used.
For example:
1 2 3 |
... # execute a function in event loop using the default executor loop.run_in_executor(None, task) |
The default is a ThreadPoolExecutor for executing blocking tasks using a thread pool.
The default can be changed by passing an instance of an executor to the set_default_executor() method.
For example:
1 2 3 4 5 6 7 |
... # create an executor exe = ProcessPoolExecutor() # set the default executor loop.set_default_executor(exe) # run a blocking function using the default executor loop.run_in_executor(None, task) |
Other Methods
We have taken a brief tour of some of the more interesting and useful methods on the event loop.
This was by no means complete.
There are many other methods on the event loop for interacting with subprocesses, streams, and callback functions.
You can see a full list of the capabilities of the event loop here:
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 asyncio event loop in Python.
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Photo by Tom Blackout on Unsplash
Ali says
Mr.Brownlee,
Thanks for another great website. I know you from machinelearningmastery.com
In some other resource, I’ve seen that once a new event loop is created, set_event_loop is called after as:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
Is this a different use-case?
Jason Brownlee says
Yes, generally this is not needed.