Asynchronous Programming

Asynchronous programming is particularly useful when your program needs to wait for tasks that don’t require active CPU processing but involve waiting for external operations to complete. These tasks could include:

  • I/O Operations: Reading from or writing to files, databases, or network connections.

  • Web Requests: Waiting for HTTP requests to complete.

  • Timers: Waiting for a specified amount of time (e.g., delays).

  • User Input: Waiting for user interaction in some contexts.

In python, async/await is refered as coroutines. The concept is very well explained, in this page we’ll just re-use the same examples

import asyncio
import time

# Simple Coroutine
async def function_A(delay, message):
    print(f"Started Function A at {time.strftime('%X')}")
    asyncio.sleep(delay)
    print(message)
    print(f"Finished Function A at {time.strftime('%X')}")

asyncio.run(function_A())

# Nested Coroutine
async def function_B(delay, message):
    print("Entering Function B")
    await function_A(1, "Salut")
    print("Exiting Function B")

asyncio.run(function_B())

# Concurrent coroutines using asyncio tasks
async def function_B(delay, message):
    print("Entering Function B")
    task1 = asyncio.create_task(function_A(1, "Call 1"))
    task2 = asyncio.create_task(function_A(2, "Call 2"))
    task3 = asyncio.create_task(function_A(3, "Call 3"))
    await task1
    await task2
    await task3
    print("Exiting Function B")

asyncio.run(function_B())

# Concurrent coroutines using asyncio tasks group
async def function_B(delay, message):
    print("Entering Function B")
    async with asyncio.TaskGroup() as tg:
        task1 = tg.create_task(function_A(1, "Call 1"))
        task2 = tg.create_task(function_A(2, "Call 2"))
        task3 = tg.create_task(function_A(3, "Call 3"))
    print("Exiting Function B")

asyncio.run(function_B())

Sources: