How do python handle asynchronous task

I've strong background in a JS frameworks. Javascript is single threaded language and during the execution of code when it encounters any async task, the event loop plays the important role there.

Now I've been into Python/Django and things are going good but I'm very curious about how python handle any asynchronous tasks whenever I need to make a database query in django, I simple use the queryset method like

def func(id):
    do_somthing()
    user = User.objects.get(id=id)
    do_somthing_with_user()


func()
another_func()

I've used the queryset method that fetch something from database and is a asynchronous function without await keyword. what will be the behaviour of python there, is it like block the execution below until the queryset response, is it continue the execution for the rest of code on another thread.

Also whenever we need to make an external request from python, we uses requests library method without await keyword,

r = request.get("https://api.jsonplaceholder.com/users")
# will the below code executes when the response of the request arrive?
do_somthing()
.
.
.
do_another_thing()

does python block the execution of code until the response arrives?

In JS the async task is handled by event loop while the rest of the code executes normally

console.log("first")

fetch("https://api.jsonplaceholder.com/users").then(() => console.log("third"))

console.log("second")

the logs in console will be

first second third

How Python Handles these type things under the hood?

In Python, network operations such as making a request to a web server or reading from a socket can block the execution of code until a response is received. This means that while the Python program is waiting for a response, it is not able to perform any other operations.

For example, if you use the urllib or requests library to make a request to a web server, the execution of the code will be blocked until a response is received:

import requests
response = requests.get("http://example.com")
print(response.text)

In this example, the execution of the code will be blocked until a response is received from the server. This can be a problem if you need to perform other operations while waiting for a response, or if the response takes a long time to arrive.

However, Python provides ways to avoid blocking the execution of code by using asyncio, a module for concurrent programming in Python. With asyncio you can use async and await keywords to define asynchronous functions and use them to perform network operations without blocking the execution of code.

For example, the aiohttp library provides an async version of the requests library, which you can use to make requests to a web server without blocking the execution of code:

import aiohttp
import asyncio

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    async with aiohttp.ClientSession() as session:
        response = await fetch(session, "http://example.com")
        print(response)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

This way the execution of the code will not be blocked and you can perform other operations while waiting for the response.

The await command needs to be used specifically when you are running an asynchronous function that calls synchronous functions. This synchronous function is ran as a coroutine and has an object name coroutine when debugging. To run a coroutine, it must be scheduled on the event loop. Using the await keyword releases control back to the event loop. Instead of awaiting a coroutine, you can also setup a task to schedule a coroutine to run at a certain time using the asyncio package. See documentation

asyncio.create_task(coro, *, name=None, context=None)

Python does not block execution but rather ensures that it happens at the correct time using CPU parallelism. Using a method like request.get() should not create any forthcoming errors because you are accessing values over the internet and not locally. Network requests move, essentially at the speed of light, where as database requests on the server are limited by the speed of the SSD or HDD. This was more of an issue on old drives where you are literally waiting for a disk to spin, but the idea of properly managing these local resources is still a issue that has to be solved in order for the asynchronous application to work. When making calls to the sql database, make sure to use the decorator @database_sync_to_async above the regular 'synchronous' functions that your asynchronous function calls.

from channels.db import database_sync_to_async

For more information check out the django documentation on asynchronous support.

Back to Top