Skip to main content Brad's PyNotes

Asyncio Module: Asynchronous Programming With Async/Await

TL;DR

Asyncio enables asynchronous programming with async/await syntax, allowing single-threaded concurrent execution perfect for I/O-bound tasks like web requests and file operations.

Interesting!

Asyncio can handle thousands of concurrent connections with minimal memory overhead - a single asyncio application can often outperform traditional threaded servers by avoiding context switching costs.

Basic Async Concepts

python code snippet start

import asyncio
import time

async def fetch_data(name, delay):
    print(f"Starting {name}")
    await asyncio.sleep(delay)  # Simulates I/O operation
    print(f"Finished {name}")
    return f"Data from {name}"

async def main():
    # Sequential execution - slow
    start = time.time()
    result1 = await fetch_data("API 1", 2)
    result2 = await fetch_data("API 2", 1)
    print(f"Sequential: {time.time() - start:.1f}s")
    
    # Concurrent execution - fast
    start = time.time()
    results = await asyncio.gather(
        fetch_data("API 3", 2),
        fetch_data("API 4", 1)
    )
    print(f"Concurrent: {time.time() - start:.1f}s")

# Run the async function
asyncio.run(main())

python code snippet end

HTTP Requests with aiohttp

python code snippet start

import aiohttp
import asyncio

async def fetch_url(session, url):
    try:
        async with session.get(url) as response:
            return await response.text()
    except Exception as e:
        return f"Error fetching {url}: {e}"

async def fetch_multiple_urls():
    urls = [
        "https://httpbin.org/delay/1",
        "https://httpbin.org/delay/2",
        "https://httpbin.org/delay/1"
    ]
    
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        return results

# This completes in ~2 seconds instead of ~4 seconds
results = asyncio.run(fetch_multiple_urls())

python code snippet end

Task Management

python code snippet start

async def background_task(name):
    while True:
        print(f"{name} working...")
        await asyncio.sleep(1)

async def main():
    # Create background tasks
    task1 = asyncio.create_task(background_task("Worker 1"))
    task2 = asyncio.create_task(background_task("Worker 2"))
    
    # Run for 5 seconds then cancel
    await asyncio.sleep(5)
    task1.cancel()
    task2.cancel()
    
    # Wait for cancellation to complete
    try:
        await task1
        await task2
    except asyncio.CancelledError:
        print("Tasks cancelled")

asyncio.run(main())

python code snippet end

Producer-Consumer Pattern

python code snippet start

async def producer(queue):
    for i in range(5):
        await asyncio.sleep(1)
        await queue.put(f"item-{i}")
        print(f"Produced item-{i}")
    await queue.put(None)  # Signal completion

async def consumer(name, queue):
    while True:
        item = await queue.get()
        if item is None:
            queue.task_done()
            break
        print(f"{name} consumed {item}")
        await asyncio.sleep(0.5)
        queue.task_done()

async def main():
    queue = asyncio.Queue(maxsize=2)
    
    # Start producer and consumers
    await asyncio.gather(
        producer(queue),
        consumer("Consumer-1", queue),
        consumer("Consumer-2", queue)
    )

asyncio.run(main())

python code snippet end

Asyncio transforms I/O-bound applications by enabling concurrent execution without the complexity of threading.

The async/await syntax used throughout this module was introduced in PEP 492 , which established the foundation for modern Python asynchronous programming. Asyncio integrates well with urllib for HTTP requests and logging for async debugging . For error handling in async code, see exception handling patterns and JSON processing for API data handling.

Reference: Python Asyncio Module Documentation