Coding Essentials: Asynchronous Programming 101

BororeBorore
3 min read

Asynchronous programming is a way that allows handling of tasks in a concurrent manner. This a perfect way of handling tasks that would block the execution of the program, operations like I/O operations e.g. API call, writing/reading files and database queries. Unlike in synchronous programming where a task is executed one at a time and if it takes time, you have to wait for it to execute to completion before going to the next task.

In asynchronous programming, tasks are executed independently, and the program can execute other tasks while waiting for the result or completion of time-consuming operations. Asynchronous programing should never be used for CPU-bound operations or any blocking code.

Why use asynchronous programming?

  • improved performance: Allows simultaneous handling of tasks without waiting for each to complete to handle the next one.

  • Non-blocking and scalability: prevents one task from holding up others and efficient for I/O bound operations like network requests.

Core Concepts

  1. Event Loop the event loop is the key component that continuously monitors tasks and decides when to execute them. It efficiently manages execution of multiple asynchronous operations without blocking the main thread. In Python, the event loop is managed my the asyncio package and in NodeJS, it inherently part of the runtime of a node app. This is what allows NodeJS to be performant while being single-threaded.

  2. Coroutine A coroutine is special type of function that can be paused and resumed. Coroutine is defined using the async def in Python.

  3. Tasks Tasks are coroutines that have been scheduled to run concurrently.

  4. Await await is used to pause the execution of a coroutine until the awaited task completes. When a coroutine is being awaited, it means it yields control back to the event loop to continue to execute other tasks while it is running it the background without blocking the main thread . The event loop will periodically checks on the awaited task for completion.

Examples

import asyncio

async def fetch_data():
    print("Fetching data starts")
    await asyncio.sleep(2) # simulates blocking task
    print("Fetching data completes")

if __name__ == "__main__":
    asyncio.run(fetch_data()) # execute the task

We useasyncio.run to execute the passed coroutine and it takes care of managing the asyncio event loop.

This how to handle multiple coroutines and execute them concurrently.

import asyncio

async def fetch_data():
    print("Fetching data starts")
    await asyncio.sleep(2) # simulates blocking task
    print("Fetching data completes")

async def fetch_data(task_name):

    print(f"{task_name}: Fetching data starts")
    await asyncio.sleep(2) # simulates blocking task
    print(f"{task_name}: Fetching data completes")

async def main():

    tasks = [
        fetch_data("Task 1"),
        fetch_data("Task 2"),
        fetch_data("Task 3"),
    ]

    await asyncio.gather(*tasks)

if __name__ == "__main__":
    # asyncio.run(fetch_data())
0
Subscribe to my newsletter

Read articles from Borore directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Borore
Borore