Asynchronous I/O Programming with Python's asyncio

Event Loop and Coroutine Execution

Asyncio provides a complete solution for asynchronous I/O programming in Python. Here's an example executing 10 concurrent simulated HTTP requests:

import asyncio
import time

async def fetch_resource(url):
    print(f"Starting fetch for {url}")
    await asyncio.sleep(2)
    print(f"Completed fetch for {url}")

async def main():
    start = time.time()
    tasks = [fetch_resource(f"https://api.example.com/data/{i}") for i in range(10)]
    await asyncio.gather(*tasks)
    print(f"Total execution time: {time.time() - start:.2f} seconds")

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

Retrieving Coroutine Return Values

To obtain return values from coroutines, create tasks and access their results:

import asyncio

async def data_fetcher(url):
    print(f"Fetching data from {url}")
    await asyncio.sleep(2)
    return {"status": "success", "data": "sample_content"}

async def main():
    task = asyncio.create_task(data_fetcher("https://api.example.com"))
    await task
    print(f"Result: {task.result()}")

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

Callback Handling with Partial Functions

Use partial functions to pass parameters to completion callbacks:

import asyncio
from functools import partial

async def process_data(url):
    print(f"Processing {url}")
    await asyncio.sleep(2)
    return f"processed_{url}"

def completion_handler(base_url, future):
    print(f"Completed processing for {base_url}")
    print("Notification sent")

async def main():
    url = "https://api.example.com/items"
    task = asyncio.create_task(process_data(url))
    task.add_done_callback(partial(completion_handler, url))
    await task
    print(f"Final result: {task.result()}")

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

Task Grouping with gather()

The gather() function allows grouping and cancellation of tasks:

import asyncio

async def api_call(endpoint):
    print(f"Calling {endpoint}")
    await asyncio.sleep(2)
    print(f"Received response from {endpoint}")

async def main():
    group_a = [api_call("https://api.a.com/resource") for _ in range(3)]
    group_b = [api_call("https://api.b.com/data") for _ in range(3)]
    
    batch_a = asyncio.gather(*group_a)
    batch_b = asyncio.gather(*group_b)
    
    await asyncio.gather(batch_a, batch_b)

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

Task Cancellation and Control

Handle task cancellation through keyboard interrupts:

import asyncio

async def long_running_operation(duration):
    print(f"Operation running for {duration}s")
    try:
        await asyncio.sleep(duration)
        print(f"Operation completed after {duration}s")
    except asyncio.CancelledError:
        print(f"Operation cancelled after {duration}s")
        raise

async def main():
    operations = [
        long_running_operation(2),
        long_running_operation(3),
        long_running_operation(4)
    ]
    
    try:
        await asyncio.gather(*operations)
    except KeyboardInterrupt:
        print("Cancelling all tasks")
        for task in asyncio.all_tasks():
            if task is not asyncio.current_task():
                task.cancel()
        await asyncio.gather(*operations, return_exceptions=True)

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

Scheduled Callback Execution

Various methods for scheduling callbacks within the event loop:

import asyncio

def event_handler(message, loop):
    print(f"Event: {message} at loop time {loop.time()}")

async def main():
    loop = asyncio.get_event_loop()
    current_time = loop.time()
    
    loop.call_soon(event_handler, "Immediate execution", loop)
    loop.call_at(current_time + 1, event_handler, "Scheduled at +1s", loop)
    loop.call_at(current_time + 2, event_handler, "Scheduled at +2s", loop)
    loop.call_later(3, event_handler, "Delayed by 3s", loop)
    
    await asyncio.sleep(4)

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

Integrating ThreadPool with Asyncio

Combine thread pools with asyncio for blocking I/O operations:

import asyncio
from concurrent.futures import ThreadPoolExecutor
import requests

def blocking_http_call(url):
    response = requests.get(url)
    return response.text[:100]

async def main():
    executor = ThreadPoolExecutor(max_workers=5)
    loop = asyncio.get_event_loop()
    
    urls = [f"https://httpbin.org/delay/{i}" for i in range(5)]
    tasks = [
        loop.run_in_executor(executor, blocking_http_call, url)
        for url in urls
    ]
    
    results = await asyncio.gather(*tasks)
    for url, result in zip(urls, results):
        print(f"URL: {url}, Result: {result}")

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

Asynchronous HTTP Client Implementation

Custom HTTP client using asyncio's socket itnerface:

import asyncio
from urllib.parse import urlparse

async def http_get(url):
    parsed = urlparse(url)
    host, path = parsed.netloc, parsed.path or "/"
    
    reader, writer = await asyncio.open_connection(host, 80)
    request = f"GET {path} HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n"
    writer.write(request.encode())
    await writer.drain()
    
    response_lines = []
    async for line in reader:
        response_lines.append(line.decode())
    
    writer.close()
    await writer.wait_closed()
    return "\n".join(response_lines)

async def main():
    urls = [f"http://httpbin.org/get?request={i}" for i in range(3)]
    tasks = [http_get(url) for url in urls]
    
    for completed in asyncio.as_completed(tasks):
        result = await completed
        print(f"Response length: {len(result)}")

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

Resource Synchronization in Coroutines

Using locks for shared resource access in asynchronous code:

import asyncio
from asyncio import Lock

shared_cache = {}
access_lock = Lock()

async def cached_resource_fetcher(key):
    async with access_lock:
        if key in shared_cache:
            return shared_cache[key]
        
        print(f"Fetching fresh data for {key}")
        await asyncio.sleep(1)  # Simulate network call
        shared_cache[key] = f"data_for_{key}"
        return shared_cache[key]

async def main():
    keys = ["user_profile", "user_profile", "system_config"]
    tasks = [cached_resource_fetcher(key) for key in keys]
    results = await asyncio.gather(*tasks)
    
    for key, result in zip(keys, results):
        print(f"Key: {key}, Result: {result}")

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

Asynchronous Queue Communication

Imlpementing producer-consumer pattern with asyncio queues:

import asyncio
from asyncio import Queue

async def producer(queue, items):
    for item in items:
        await queue.put(item)
        print(f"Produced: {item}")
        await asyncio.sleep(0.1)
    await queue.put(None)  # Sentinel value

async def consumer(queue):
    while True:
        item = await queue.get()
        if item is None:
            break
        print(f"Consumed: {item}")
        await asyncio.sleep(0.2)

async def main():
    queue = Queue(maxsize=3)
    items = [f"item_{i}" for i in range(10)]
    
    await asyncio.gather(
        producer(queue, items),
        consumer(queue)
    )

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

Tags: Asyncio Coroutines async-await Concurrency event-loop

Posted on Fri, 10 Jul 2026 16:56:58 +0000 by risi