Asynchronous Programming With Python: A Simple Overview

Asynchronous Programming With Python: A Simple Overview

In programming, two primary ways to handle tasks are synchronously and asynchronously. Synchronous programming is a form of programming that lets you run only one task at a time. It is the traditional way of handling tasks. If you call a synchronous function in your code, all other aspects of your code will pause until your function runs completely, no matter how long it takes.

Asynchronous programming is a more prudent approach to handling tasks. In asynchronous programming, multiple tasks can run concurrently. This means that if a specific task or function will take a lot of time, other functions will not need to wait for it; they can run in parallel.

This article will show you how asynchronous programming works in Python.

What is Asynchronous Programming?

Asynchronous programming is a form of programming that involves running multiple tasks or functions together without one function waiting for another to complete its process.

Asynchronous programming is a multi-threaded model so it is non-blocking, unlike synchronous programming.

For more context, think of asynchronous programming like downloading multiple files from the internet simultaneously. You can decide to initiate a download for all the files at the same time. The download times of each file might differ from each other however, other files are not blocked from downloading because one file is downloading.

On the other hand, in a synchronous approach, you can only download one file at a time.

How to Implement Asynchronous Programming in Python

Traditionally, Python runs on a synchronous model. To write Python code asynchronously, you need to use the async and await keywords introduced in Python 3,4 through the asyncio library.

Async/Await in Python

In Python, you use the async keyword to define an asynchronous function (also called a coroutine) and use the await keyword to pause your async (or asynchronous) function so another async function can execute its processes. You can only use the await keyword inside an asynchronous function.

Here’s what the syntax for an asynchronous function looks like in Python:

async def my_async_func():
    # do some stuff
    await my_other_async_func() # pauses my_async_func for my_other_async_func to run
    # do some other stuff

To illustrate the difference between a synchronous function and an asynchronous function, consider the following example:

import time

def task(name):
    print(f'Starting task {name}')
    time.sleep(2)  # Simulating a time-consuming task
    print(f'Task {name} completed')

def main():
    """
    entry point for your program
    """
    start_time = time.time()
    task('A')
    task('B')
    task('C')
    end_time = time.time()
    print(f'Total time taken: {end_time - start_time} seconds')

if __name__ == "__main__":
    main()

The code above defines a function (or routine) called task. It uses the time.sleep() function to simulate a task that takes two seconds to complete.

The second function is called main. It is the entry point for the program. It simply calls the task function three times and prints the total time to complete the three tasks.

If you run the code above, you will get something like this:

In the above picture, the tasks take six seconds to complete.

Imagine you have an application where it takes two seconds to respond to a user’s request. If you follow this synchronous method, it means your first user gets a response in two seconds, the second user gets a response in four seconds and the third user gets a response in six seconds. If you have 100 users making a request, you can imagine how much time it will take to respond to all your users.

An alternative approach is to use the asynchronous method. Here is a rewritten version of the previous code using async and await:

import asyncio
import time

async def task(name):
    print(f'Starting task {name}')
    await asyncio.sleep(2)  # Simulating a time-consuming task
    print(f'Task {name} completed')

async def main():
    start_time = time.time()
    await asyncio.gather(task('A'), task('B'), task('C'))
    end_time = time.time()
    print(f'Total time taken: {end_time - start_time} seconds')

if __name__ == "__main__":
    asyncio.run(main())

This version of the code uses the asyncio library. The asyncio library makes asynchronous programming possible in Python by providing many asynchronous functions you can leverage in your code.

The second line of the new task function calls the asyncio.sleep() function. The difference between this and the time.sleep() method is that asyncio.sleep() is asynchronous. Hence, you have to call it with the await keyword.

In the main function, asyncio.gather() is used to execute the three asynchronous functions concurrently.

If you run the code above you should get something similar to this:

In the picture above, you can see that all three functions take only two seconds to execute. This behaviour is because when you call asyncio.gather(), it takes a number of tasks and schedules them concurrently. This means that the three tasks start running almost at the same time.

Also, because the task function awaits the asyncio.sleep() method, task(’B’) and task('C') can progress concurrently while task('A') pauses for two seconds.

When To Use Asynchronous Functions in Programming

Now that you understand the basic difference between synchronous and asynchronous programming, you should know that you can’t use asynchronous programming every time. This section will highlight some areas where asynchronous programming is the better option.

Real-time Applications

Real-time applications are applications that need to receive updates in real time. Some examples of real-time applications include chat applications such as WhatsApp or Slack. Other examples include live dashboards or gaming backends for real-time multiplayer communication.

If you need to build applications in this category, you will need to use asynchronous programming.

I/O-Heavy Operations

I/O-heavy operations are tasks where the system spends a significant amount of time waiting for an external input or output operation to complete. This can happen if your program needs to communicate with external resources. such as reading or writing to large external files, database queries, or even network calls.

The above operations tend to cause a block or delay in your program if you use synchronous programming, so you are better off using asynchronous programming.

Background Tasks and Scheduling

Sometimes in your code, you might want to schedule a task to happen later or to happen in the background, such as sending emails periodically or uploading a large file in the background. To prevent this from disturbing other parts of your code, you should assign such tasks to asynchronous functions.

Conclusion

Asynchronous programming is a powerful concept in programming and Python makes it possible to apply it in your everyday code. You should try to practice using asynchronous programming in your code for better familiarity and competence. Let me know how that goes on X!