Difficulty: Advanced
Explain async/await in Python. How does asyncio work?
asyncio is Python's built-in library for writing concurrent code using async/await syntax.
Key concepts: - Coroutine: a function defined with 'async def', returns a coroutine object - await: suspends execution until the awaited coroutine completes - Event loop: runs coroutines, handles I/O events, manages tasks - Task: a wrapper that schedules a coroutine to run concurrently - asyncio.gather(): run multiple coroutines concurrently
async/await is ideal for I/O-bound operations (HTTP requests, database queries, file I/O) where you spend most time waiting. It uses a single thread with cooperative multitasking.
import asyncio
async def greet(name, delay):
"""A coroutine that simulates async work."""
print(f"Starting greeting for {name}")
await asyncio.sleep(delay) # Non-blocking sleep
print(f"Hello, {name}! (after {delay}s)")
return f"Greeted {name}"
async def main():
# Sequential: 3 seconds total
print("=== Sequential ===")
result1 = await greet("Alice", 1)
result2 = await greet("Bob", 2)
print(f"Results: {result1}, {result2}")
# Concurrent: 2 seconds total (max of delays)
print("\n=== Concurrent ===")
results = await asyncio.gather(
greet("Charlie", 1),
greet("Diana", 2),
greet("Eve", 1.5),
)
print(f"Results: {results}")
asyncio.run(main())
Sequential await blocks until each coroutine finishes. asyncio.gather() runs them concurrently - total time is the maximum, not the sum.
import asyncio
async def fetch_data(url, delay):
print(f"Fetching {url}...")
await asyncio.sleep(delay)
if 'error' in url:
raise ValueError(f"Failed to fetch {url}")
return f"Data from {url}"
async def main():
# Create tasks for concurrent execution
task1 = asyncio.create_task(fetch_data('api/users', 1))
task2 = asyncio.create_task(fetch_data('api/posts', 2))
# Tasks start immediately, await collects results
result1 = await task1
result2 = await task2
print(f"Got: {result1}, {result2}")
# Error handling with gather
results = await asyncio.gather(
fetch_data('api/ok', 1),
fetch_data('api/error', 1),
return_exceptions=True # Don't raise, return exceptions
)
for r in results:
if isinstance(r, Exception):
print(f"Error: {r}")
else:
print(f"Success: {r}")
# Timeout
try:
result = await asyncio.wait_for(
fetch_data('api/slow', 10),
timeout=2.0
)
except asyncio.TimeoutError:
print("Request timed out!")
asyncio.run(main())
create_task() starts a coroutine running immediately. return_exceptions=True in gather prevents one failure from canceling others. wait_for() adds timeouts.
import asyncio
# Async context manager
class AsyncDatabase:
async def __aenter__(self):
print("Connecting to database...")
await asyncio.sleep(0.5) # Simulate connection
print("Connected!")
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
print("Closing connection...")
await asyncio.sleep(0.1)
print("Connection closed.")
return False
async def query(self, sql):
await asyncio.sleep(0.2)
return f"Results for: {sql}"
# Async iterator
class AsyncCounter:
def __init__(self, limit):
self.limit = limit
self.count = 0
def __aiter__(self):
return self
async def __anext__(self):
if self.count >= self.limit:
raise StopAsyncIteration
self.count += 1
await asyncio.sleep(0.1)
return self.count
async def main():
# Async with
async with AsyncDatabase() as db:
result = await db.query("SELECT * FROM users")
print(result)
# Async for
async for num in AsyncCounter(5):
print(f"Count: {num}", end=' ')
print()
# Async comprehension
values = [num async for num in AsyncCounter(3)]
print(f"Values: {values}")
asyncio.run(main())
async with uses __aenter__/__aexit__ for async resource management. async for uses __aiter__/__anext__ for async iteration. Both are essential for async database and HTTP patterns.
asyncio, async/await, Coroutines, Tasks, Event Loop, aiohttp