In Python 3.4 and later, a new event loop called asyncio was added to the standard library. This new event loop provides a way to write highly concurrent and asynchronous applications in Python. In this article, we will take a closer look at asyncio and provide code examples to help you get started.
What is an Event Loop?
An event loop is a programming construct that waits for and dispatches events or messages in a program. It is often used in graphical user interfaces (GUIs), where the user interacts with a graphical widget and the widget communicates with the program via an event loop. In a GUI application, the event loop waits for user actions, such as mouse movements or button clicks, and dispatches events to the appropriate handlers in the program.
In the context of asyncio, the event loop is used to manage asynchronous tasks or coroutines. The event loop waits for coroutines to yield control, and when a coroutine yields, the event loop schedules the next coroutine to run. This allows multiple coroutines to run concurrently without blocking each other. In addition, the event loop provides utilities to handle I/O operations, such as reading and writing to sockets or files, without blocking the main thread.
How to Use asyncio
To use asyncio, we need to create an event loop and run coroutines on it. We can create an event loop object by calling asyncio.get_event_loop(). We then use the event loop object to run our coroutines, which can be defined using the async def syntax.
Here is an example of a coroutine that prints a message and then waits for a second before returning:
import asyncio
async def my_coroutine():
print("Hello, world!")
await asyncio.sleep(1)
print("Goodbye, world!")
loop = asyncio.get_event_loop()
loop.run_until_complete(my_coroutine())
loop.close()
In this example, we create a coroutine called my_coroutine(). Inside the coroutine, we print a message to the console using print(). We then call await asyncio.sleep(1) to tell the event loop to wait for one second before continuing. Finally, we print another message and exit the coroutine.
To run the coroutine, we create an event loop by calling asyncio.get_event_loop(), and then we call the run_until_complete() method of the event loop object and pass in the coroutine as an argument. This tells the event loop to run the coroutine until it completes. After the coroutine finishes, we close the event loop by calling its close() method.
Concurrency with asyncio
One of the main benefits of asyncio is its ability to run multiple coroutines concurrently without blocking each other. Here is an example of how to run two coroutines concurrently:
import asyncio
async def my_coroutine1():
print("Hello from coroutine 1!")
await asyncio.sleep(1)
print("Goodbye from coroutine 1!")
async def my_coroutine2():
print("Hello from coroutine 2!")
await asyncio.sleep(2)
print("Goodbye from coroutine 2!")
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(my_coroutine1(), my_coroutine2()))
loop.close()
In this example, we define two coroutines, my_coroutine1() and my_coroutine2(). Each coroutine prints a message to the console, waits for a certain amount of time, and then prints another message. We then use asyncio.gather() to run both coroutines concurrently. asyncio.gather() takes two or more coroutines as arguments and returns a Future object that represents the result of all the coroutines. We pass the Future object to run_until_complete() to run the coroutines until they complete.
Handling I/O with asyncio
In addition to running coroutines concurrently, asyncio also provides utilities to handle I/O operations without blocking the main thread. For example, we can use asyncio.open_connection() to establish a connection to a remote server without blocking:
import asyncio
async def read_data(reader):
data = await reader.read(100)
print("Received:", data.decode())
async def write_data(writer):
writer.write(b"Hello, world!")
await writer.drain()
async def main():
reader, writer = await asyncio.open_connection("google.com", 80)
await write_data(writer)
await read_data(reader)
writer.close()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
In this example, we define two coroutines called read_data() and write_data(). read_data() reads up to 100 bytes from a reader object and prints the data to the console. write_data() writes the bytes "Hello, world!" to a writer object and then waits for the writer to drain its buffer.
We then define a coroutine called main() that establishes a connection to the Google server using asyncio.open_connection(). We pass the reader and writer objects to read_data() and write_data(), respectively, and then call them using await.
After the coroutines finish, we close the writer object by calling its close() method.
Conclusion
In this article, we introduced asyncio, a new event loop in Python that allows us to write highly concurrent and asynchronous applications. We provided code examples to demonstrate how to create coroutines, run them concurrently, and handle I/O operations without blocking the main thread. With asyncio, we can write Python applications that are more scalable, performant, and responsive.
Sure! Let's dive deeper into asyncio and the concepts we've already introduced.
Coroutines in asyncio
A coroutine is a lightweight form of concurrency that allows us to write code that looks like synchronous code but executes asynchronously. In asyncio, we use the async def syntax to create coroutines. When a coroutine is called, it returns a coroutine object that can be scheduled on the event loop.
Here's an example of a coroutine that uses an asyncio.sleep() call:
import asyncio
async def my_coroutine():
print("Starting coroutine")
await asyncio.sleep(1)
print("Coroutine finished")
In this example, we define a coroutine called my_coroutine() that prints a message, waits for one second using asyncio.sleep(), and then prints another message.
The await
keyword is used to suspend the coroutine while it waits for the sleep call to finish. During this time, the event loop can switch to another coroutine that is ready to run.
Event Loop in asyncio
The event loop is the core of the asyncio module and provides the foundation for running concurrent code. Event loops manage coroutines and ensures they are executed in the correct order. When we want to run coroutines, we first need to create an event loop.
To create an event loop, we use the get_event_loop()
method provided by the asyncio
module. Here's an example:
import asyncio
loop = asyncio.get_event_loop()
In this example, we create an event loop by calling get_event_loop()
and store it in the variable named loop
.
Once we have an event loop object, we can use it to run our coroutines. The run_until_complete()
method is then used to execute a coroutine until it completes. Here is an example:
loop.run_until_complete(my_coroutine())
This tells the event loop to run the my_coroutine()
coroutine until completion.
Running Multiple Coroutines Concurrently in asyncio
Asyncio allows us to run multiple coroutines concurrently without blocking the event loop. This means that we can run multiple coroutines at the same time, and the event loop will handle the scheduling of these coroutines.
One way to run multiple coroutines concurrently is to use the asyncio.gather()
method. The gather()
method takes multiple coroutines as input, ties them together, and schedules them to run concurrently. Here's an example:
async def coroutine_1():
print('Coroutine 1')
await asyncio.sleep(1)
print('Coroutine 1 done')
async def coroutine_2():
print('Coroutine 2')
await asyncio.sleep(1)
print('Coroutine 2 done')
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(coroutine_1(), coroutine_2()))
In this example, we define two coroutines called coroutine_1()
and coroutine_2()
. Both coroutines print a message, wait for one second, print another message, and then exit.
We then create an event loop and use the gather()
method to run both coroutines concurrently. When we run the event loop with run_until_complete(asyncio.gather(coroutine_1(), coroutine_2()))
, the event loop will run both coroutines, even though they are both "sleeping" for 1 second.
Handling I/O with asyncio
Asyncio also provides tools for handling I/O operations, such as reading and writing to files and sockets. These operations can be done asynchronously using coroutines.
For example, we can use asyncio.open_connection()
to open a socket connection and send data to a server. Here's an example:
async def send_data():
reader, writer = await asyncio.open_connection('localhost', 12345)
writer.write(b'Hello, world!
')
await writer.drain()
writer.close()
await writer.wait_closed()
loop = asyncio.get_event_loop()
loop.run_until_complete(send_data())
In this example, we create a coroutine called send_data()
that opens a socket connection to localhost
on port 12345
, sends a message to the server, and then closes the connection.
The await writer.drain()
line is used to make sure that all data that has been written to the socket is actually sent before continuing.
Conclusion
In this article, we have explored asyncio in Python, and how it can be used to write highly concurrent and asynchronous applications. We looked at coroutines as a way to execute asynchronous code, event loops as a way to manage execution, and multiple ways to run coroutines concurrently and handle I/O operations. By adopting asyncio when writing Python applications, it is possible to write code that is more performant, responsive, and scalable.
Popular questions
- What is asyncio?
Asyncio is a new event loop added to the Python standard library in version 3.4 and later. It provides a way to write highly concurrent and asynchronous applications in Python.
- How do you create a coroutine in asyncio?
In asyncio, coroutines are created using the async def
syntax. Here's an example:
import asyncio
async def my_coroutine():
print("Starting coroutine")
await asyncio.sleep(1)
print("Coroutine finished")
- How do you run a coroutine in asyncio?
To run a coroutine in asyncio, you need to create an event loop and use its run_until_complete()
method. Here's an example:
loop = asyncio.get_event_loop()
loop.run_until_complete(my_coroutine())
This tells the event loop to run the my_coroutine()
coroutine until it finishes.
- How do you run multiple coroutines concurrently in asyncio?
Asyncio allows you to run multiple coroutines concurrently using the asyncio.gather()
method. The gather()
method takes multiple coroutines as input, ties them together, and schedules them to run concurrently. Here's an example:
async def coroutine_1():
print('Coroutine 1')
await asyncio.sleep(1)
print('Coroutine 1 done')
async def coroutine_2():
print('Coroutine 2')
await asyncio.sleep(1)
print('Coroutine 2 done')
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(coroutine_1(), coroutine_2()))
This will run both coroutines concurrently, even though they are both "sleeping" for 1 second.
- How can asyncio be used to handle I/O operations?
Asyncio provides tools for handling I/O operations, such as reading and writing to files and sockets. These operations can be done asynchronously using coroutines. For example, you can use asyncio.open_connection()
to open a socket connection and send data to a server. Here's an example:
async def send_data():
reader, writer = await asyncio.open_connection('localhost', 12345)
writer.write(b'Hello, world!
')
await writer.drain()
writer.close()
await writer.wait_closed()
loop = asyncio.get_event_loop()
loop.run_until_complete(send_data())
This will open a socket connection to localhost
on port 12345
, send a message to the server, and then close the connection.
Tag
Asyvent