import asyncio
async def fetch_data():
"""Simulate fetching data from API"""
print("Starting to fetch...")
await asyncio.sleep(2) # Simulates I/O operation
print("Data fetched!")
return {"data": "example"}
44 Async Programming (Intro)
Modern versions of Python have support for “asynchronous code” using something called “coroutines”, with async
and await
syntax.
Asynchronous programming allows your program to work on multiple tasks without blocking. Think of it like a restaurant kitchen - instead of waiting for one dish to cook completely before starting another, the chef starts multiple dishes and tends to them as needed.
44.1 Key Concepts
44.1.1 Coroutines
A coroutine is a special function defined with async def
. It can pause and resume execution.
fetch_data()
<coroutine object fetch_data at 0x107d12f80>
44.1.2 The Event Loop
The event loop is the core of async programming - it manages and executes coroutines.
┌─────────────────────────────────────┐
│ EVENT LOOP │
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ Task 1 │ │ Task 2 │ ... │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ ▼ ▼ │
│ [Running] [Waiting] │
│ │
└─────────────────────────────────────┘
44.2 Basic Example
import asyncio
async def say_hello(name, delay):
"""Greet after delay"""
await asyncio.sleep(delay)
print(f"Hello, {name}!")
Hello, Bob!
Hello, Alice!
Hello, Charlie!
# Concurrent
async def main():
"""Run multiple greetings concurrently"""
# Create tasks
= asyncio.create_task(say_hello("Alice", 2))
task1 = asyncio.create_task(say_hello("Bob", 1))
task2 = asyncio.create_task(say_hello("Charlie", 3))
task3
# Wait for all tasks concurrently
await asyncio.gather(task1, task2, task3)
# Run the async program
await main()
Hello, Bob!
Hello, Alice!
Hello, Charlie!
Time →
0s 1s 2s 3s
│ │ │ │
├───────┼───────┼───────┤
│ │
Task1: [====await====][done]
│ │
Task2: [=await=][done] │
│ │
Task3: [======await======][done]
↑ ↑ ↑
│ │ │
Bob says Alice Charlie
hello says says
hello hello
44.3 Example: Fetching Multiple URLs
import asyncio
import aiohttp
import time
async def fetch_url(session, url):
"""Fetch a single URL"""
async with session.get(url) as response:
return await response.text()
# Sequential
async def fetch_all_sync_style():
"""Fetch URLs one by one (slow)"""
= [
urls "https://api.github.com/users/python",
"https://api.github.com/users/github",
"https://api.github.com/users/torvalds"
]
async with aiohttp.ClientSession() as session:
for url in urls:
= await fetch_url(session, url)
data print(f"Fetched {len(data)} bytes from {url}")
# Concurrent
async def fetch_all_async_style():
"""Fetch URLs concurrently (fast)"""
= [
urls "https://api.github.com/users/python",
"https://api.github.com/users/github",
"https://api.github.com/users/torvalds"
]
async with aiohttp.ClientSession() as session:
= [fetch_url(session, url) for url in urls]
tasks = await asyncio.gather(*tasks)
results
for url, data in zip(urls, results):
print(f"Fetched {len(data)} bytes from {url}")
# Compare performance
async def main():
print("Synchronous style:")
= time.time()
start await fetch_all_sync_style()
print(f"Time: {time.time() - start:.2f}s\n")
print("Asynchronous style:")
= time.time()
start await fetch_all_async_style()
print(f"Time: {time.time() - start:.2f}s")
await main()
Synchronous style:
Fetched 1263 bytes from https://api.github.com/users/python
Fetched 1241 bytes from https://api.github.com/users/github
Fetched 1223 bytes from https://api.github.com/users/torvalds
Time: 1.02s
Asynchronous style:
Fetched 1263 bytes from https://api.github.com/users/python
Fetched 1241 bytes from https://api.github.com/users/github
Fetched 1223 bytes from https://api.github.com/users/torvalds
Time: 0.12s
44.4 Common Patterns
44.4.1 Using asyncio.gather()
async def task_a():
"""First task"""
await asyncio.sleep(1)
return "Result A"
async def task_b():
"""Second task"""
await asyncio.sleep(2)
return "Result B"
async def main():
"""Run tasks concurrently"""
= await asyncio.gather(task_a(), task_b())
results print(results) # ['Result A', 'Result B']
float = time.time()
start: await main()
print(f"Time: {time.time() - start:.2f}s")
['Result A', 'Result B']
Time: 2.00s
44.4.2 Using asyncio.create_task()
async def background_task():
"""Long running task"""
while True:
print("Background work...")
await asyncio.sleep(5)
async def main():
"""Main with background task"""
# Start background task
= asyncio.create_task(background_task())
task
# Do other work
await asyncio.sleep(10)
# Cancel background task
task.cancel()
float = time.time()
start: await main()
print(f"Time: {time.time() - start:.2f}s")
Background work...
Background work...
Time: 10.00s
44.4.3 Async Context Managers
class AsyncResource:
"""Example async context manager"""
async def __aenter__(self):
print("Acquiring resource...")
await asyncio.sleep(1)
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
print("Releasing resource...")
await asyncio.sleep(0.5)
async def main():
"""Use async context manager"""
async with AsyncResource() as resource:
print("Using resource")
float = time.time()
start: await main()
print(f"Time: {time.time() - start:.2f}s")
Acquiring resource...
Using resource
Releasing resource...
Time: 1.50s
44.5 Concurrent vs Parallelism
Concurrent (Async/Await) -> I/O-bound operation
Parallelism -> CPU-bound operation
Feature | Synchronous | Asynchronous |
---|---|---|
Definition | Executes one task at a time | Can handle multiple tasks concurrently |
Syntax | def function(): |
async def function(): |
Calling | result = function() |
result = await function() |
Blocking | Blocks until complete | Non-blocking, can switch tasks |
Best for | CPU-bound tasks | I/O-bound tasks |
Complexity | Simpler to understand | More complex flow |
Performance | Slower for I/O tasks | Faster for concurrent I/O |