Python

How to Implement Asynchronous Programming in Python

Python’s asynchronous programming is a versatile approach that facilitates the programmers and developers to write programs that execute concurrently without disturbing the main thread. This is useful for applications that must manage simultaneous program calls or processes such as web servers, online chat applications, and data processing systems.

Asynchronous I/O Operations

Asynchronous programming is frequently utilized in conjunction with non-blocking, I/O tasks like reading and writing via sockets. This makes it possible for I/O operations to be completed without pausing the main thread which can improve the responsiveness and performance of the application as a whole. For I/O-bound activities, asynchronous programming can be particularly advantageous. The “await” keyword can be used with functions that conduct I/O activities such as sending requests over the network, reading and writing files, or working with databases.

Asyncio Library

Python provides the “asyncio” built-in library to implement the asynchronous programming. We can use it to manage and schedule the asynchronous tasks.

Async and Await Keywords

Python was created with “await” and “async” to support the asynchronous programming. Async def functions are asynchronous and could be paused using the “await” keyword in mid-execution.

Event Loop

To use the asyncio module, programmers must first create an event loop which is an object that controls how the asynchronous operations are carried out. After this loop is developed reasonably, we can begin creating asynchronous jobs. To do this, write the functions that now incorporate the “async” keywords.

Coroutines

Coroutines are asynchronous functions. Coroutines are unique processes with pause and resume capabilities. They can now operate in tandem with other coroutines, thanks to this.

Developers use the “await” keyword to halt a coroutine. This allows the event loop to resume running the main thread instead of the coroutine. When it’s ready, the event loop continues with the coroutine.

Scenario 1:

Here’s a basic example of an asynchronous chat application scenario. Assume that we are developing a real-time message-sending and receiving chat application for customers. Writing this code synchronously prevents the main thread from operating until the message is transmitted or received. This implies that before the user could see any modifications to the chat window, they must patiently wait for the whole transaction to finish. Alternatively, we might manage the message sending and receiving using asynchronous programming. This lets the user to stay engaged with the chat window while sending and receiving messages.

To enable the real-time message sending and receiving, let’s create a basic chat client and server in this example.

ASynch.py:

import asyncio
# Client Code
async def func_send_message_to_server():
    #Open Asynchronious connection, SERVER IP AND PORT
    reader, writer = await asyncio.open_connection('IP/SERVER', PORT)
    #forever loop
    while True:
        #Record User Choice
        c_input_msg = input("Enter a message for Server (or 'quit' to exit): ")
        if c_input_msg == 'quit': #Break the Loop
            break
        #Write Message for Server using Command Prompt
        writer.write(c_input_msg.encode())
        await writer.drain()
        #display message receive from server
        msg_serv_for_client = await reader.read(100)
        print(f"Message from Server is : {msg_serv_for_client.decode()}")
    #close the writer
    writer.close()
    await writer.wait_closed()
# Server Code
async def func_handle_request_client(reader, obj_writer):
    while True:
        #Read the CLient Message
        c_msg = await reader.read(100)
        #decode the message sent by client
        c_mesg_decoded = c_msg.decode()
        #If no message break the loop
        if not c_msg:
            break
        #Display the Message of the Client
        print(f"Message Received from Newly Connected Client: {c_mesg_decoded}")
        #Create a Message from Server to Client
        srv_response_msg = f" {c_mesg_decoded}"
        #Send message to client after encoding
        obj_writer.write(srv_response_msg.encode())
        await obj_writer.drain()
    #Print Message server is disconnected
    print("Client is disconnected successfully.")
    #Close the writer object
    obj_writer.close()
#Method for creating Asynhcronous Server
async def main():
    #start the Asynhcronous Server
    asynch_srv = await asyncio.start_server(func_handle_request_client, 'IP/SERVER', PORT)
    #Synch Server
    async with asynch_srv:
        await asynch_srv.serve_forever()
if __name__ == "__main__":
    loop = asyncio.get_event_loop()  # Get the event loop
    #Call the main method Asynchroniously
    ser_obj_task = asyncio.ensure_future(main())
    objclient_task = asyncio.ensure_future(func_send_message_to_server())
    #Handle user inputs
    try:
        loop.run_until_complete(asyncio.gather(ser_obj_task, objclient_task))
    except KeyboardInterrupt:
        pass
    finally:
        loop.run_until_complete(loop.shutdown_asyncgens())

In this example:

  • We use the asyncio.start_server to define a server and the asyncio.open_connection to define a client.
  • On the server side, the func_handle_request_client coroutine is always listening for incoming client messages and sends back an acknowledgment.
  • The user can send messages to the server via the client-side func_send_message_to_server coroutine. To quit the client, type “quit”.
  • Asyncio.create_task allows for concurrent operation of the server and client.
  • Asyncio.run is used to run the client and server activities simultaneously.

This straightforward example shows how asyncio’s asynchronous programming enables independent message sending and receiving between the client and server without stalling the main thread which makes it appropriate for live communication applications.

Output:

This shows that when the program is run, the system asks the user to enter either a message or quit to exit the program. When the user types a message and hits the “Enter” button on the command line, this message will be delivered to the server and resends the same message back in response to the client server.

This is the updated code that uses aiohttp to fetch the data asynchronously from several servers:

Pip install aiohttp

Scenario 2:

Here is another simple example scenario that shows how to use the asynchronous programming in Python.

Let’s say we are developing a web-based application solution to retrieve a content from two websites and present it to the user. Although writing this code synchronously prevents the main thread from operating until both sites have been fetched, it is a possibility. In other words, before the user can see any results, they must patiently wait for the entire operation to finish.

Alternatively, we may retrieve the content from both websites simultaneously using asynchronous programming. By doing this, the user wouldn’t have to wait for the full process to finish in order to view the results immediately as they are available.

ASynchEx.py:

import asyncio
import aiohttp
async def fetch_website_content(web_url):
    async with aiohttp.ClientSession() as client_session:
        async with client_session.get(web_url) as url_response:
            if url_response.status == 200:
                return await url_response.text()
            else:
                return None
async def main():
    examples_urls_for_HTML = ['https://example.com', 'https://example.net']
    tasks = [fetch_website_content(web_url) for web_url in examples_urls_for_HTML]
    web_content = await asyncio.gather(*tasks)
    for i, content in enumerate(web_content, 1):
        if content is not None:
            print(f"Content from website {i}:\n{content}")
        else:
            print(f"Failed to fetch content from website {i}")
if __name__ == '__main__':
    asyncio.run(main())

In this code:

1. To build an asynchronous HTTP client connection (ClientSession) to conduct requests, we import the “aiohttp” library.

2. Using “aiohttp”, the fetch_website_content() coroutine asynchronously retrieves the content of a given URL. If the HTTP status is 200 (OK), it returns the content as a string. If the request encounters a problem, it returns “None”.

3. We generate a list of URLs from which you wish to retrieve the material in the main() coroutine.

4. Using a list comprehension, we generate a list of tasks to retrieve the content from each URL simultaneously.

5. To complete all activities simultaneously and wait for their results, we utilize the asyncio.gather().

6. Lastly, we iterate through the outcomes, printing each website’s content if the fetch was successful or a failure signal if the request was unsuccessful.

With the help of this code, our web application will be able to retrieve the content that is given in the links from multiple websites at once and show the user the results as soon as they are available, giving the user a more responsive experience.

Content from the first website:

Content obtained from the second website:

A computer screen shot of text Description automatically generated

Conclusion

Asynchronous programming is a powerful paradigm that enables building more efficient, responsive, and scalable programs. It is helpful for web servers, chat applications, and data processing systems that need to handle multiple requests or tasks simultaneously. We added two unique examples to demonstrate this concept. These examples can be amended as per user choice.

About the author

Kalsoom Bibi

Hello, I am a freelance writer and usually write for Linux and other technology related content