Asyncio and Concurrency¶
Asynchronous programming enables writing concurrent programs using an event loop, allowing applications to handle thousands of input/output (I/O) bound operations without multi-threading overhead.
In Python, the asyncio module provides the framework for writing asynchronous code using the async and await keywords.
Synchronous vs Asynchronous Execution¶
- Synchronous (Blocking): Tasks execute sequentially. If a task is waiting for a response from an external network server or database, the entire thread pauses and blocks.
- Asynchronous (Non-blocking): While one task waits for an I/O operation to complete, the event loop switches to execute other pending tasks.
Coroutines: async def and await¶
- A function defined with
async defis a coroutine function. Calling it returns a coroutine object without executing the body immediately. - The
awaitexpression suspends the execution of the coroutine until the awaited result is ready, returning control to the event loop.
Example: Basic Coroutine with asyncio.run()
import asyncio
async def fetch_record(record_id: int, delay: float):
print(f"Starting fetch for record {record_id}...")
await asyncio.sleep(delay) # Non-blocking sleep
print(f"Completed fetch for record {record_id}")
return {"id": record_id, "data": "Sample Payload"}
async def main():
result = await fetch_record(101, 1.0)
print("Result:", result)
# Starts the event loop and runs main()
asyncio.run(main())
Output
Starting fetch for record 101... Completed fetch for record 101 Result: {'id': 101, 'data': 'Sample Payload'}
Running Tasks Concurrently (asyncio.TaskGroup)¶
In Python 3.11+, asyncio.TaskGroup provides an exception-safe context manager for running multiple asynchronous tasks concurrently:
Example: Running Multiple Tasks Concurrently
import asyncio
import time
async def download_data(source: str, delay: float):
print(f"Downloading from {source}...")
await asyncio.sleep(delay)
return f"Data from {source}"
async def main():
start_time = time.perf_counter()
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(download_data("Server A", 1.5))
task2 = tg.create_task(download_data("Server B", 1.0))
task3 = tg.create_task(download_data("Server C", 0.5))
# All tasks complete concurrently
print(f"Elapsed Time: {time.perf_counter() - start_time:.2f} seconds")
print("Task 1 Result:", task1.result())
asyncio.run(main())
Output
Downloading from Server A... Downloading from Server B... Downloading from Server C... Elapsed Time: 1.51 seconds Task 1 Result: Data from Server A
Concurrency Comparison: Asyncio vs Threading vs Multiprocessing¶
| Model | Best For | GIL Impact | Resource Overhead |
|---|---|---|---|
asyncio | High-volume network I/O, Web APIs, WebSockets | Single thread bound | Very low |
threading | I/O bound tasks with synchronous libraries | Bound by GIL | Moderate |
multiprocessing | CPU-bound computations (data science, encoding) | Bypasses GIL (separate processes) | High (memory per process) |