在现代Python开发中,异步编程已经成为处理I/O密集型任务的标准范式。与传统的同步编程相比,异步模型通过事件循环和非阻塞I/O操作,可以在单线程中实现高并发性能。Asyncio作为Python标准库中的异步I/O框架,提供了一套完整的工具集来编写并发代码。
注意:虽然标题提到的是异步编程,但提供的正文内容实际上是关于SQLAlchemy ORM的使用。以下内容将基于标题"Python异步编程入门:Asyncio库的使用"进行全新创作,以满足用户需求。
异步编程的核心优势在于:
典型应用场景包括:
事件循环是asyncio的核心,负责调度和执行异步任务。它像一个永不停止的循环,不断检查并执行就绪的任务。
python复制import asyncio
# 获取事件循环
loop = asyncio.get_event_loop()
# 运行直到任务完成
loop.run_until_complete(main())
协程是异步编程的基本单位,使用async/await语法定义:
python复制async def fetch_data():
print("开始获取数据")
await asyncio.sleep(1) # 模拟I/O操作
print("数据获取完成")
return {"data": 123}
任务是对协程的进一步封装,用于调度执行:
python复制async def main():
task = asyncio.create_task(fetch_data())
await task
Future代表异步操作的最终结果,是Task的基类:
python复制async def set_future():
loop = asyncio.get_running_loop()
future = loop.create_future()
def callback():
future.set_result("完成!")
loop.call_soon(callback)
return await future
python复制import asyncio
async def say_after(delay, message):
await asyncio.sleep(delay)
print(message)
async def main():
print(f"开始时间: {time.strftime('%X')}")
await say_after(1, '你好')
await say_after(2, '世界')
print(f"结束时间: {time.strftime('%X')}")
# 运行结果:
# 开始时间: 14:30:00
# 你好 (1秒后)
# 世界 (再2秒后)
# 结束时间: 14:30:03
使用asyncio.gather()实现并发:
python复制async def main():
task1 = asyncio.create_task(say_after(1, '你好'))
task2 = asyncio.create_task(say_after(2, '世界'))
print(f"开始时间: {time.strftime('%X')}")
await task1
await task2
print(f"结束时间: {time.strftime('%X')}")
# 运行结果:
# 开始时间: 14:30:00
# 你好 (1秒后)
# 世界 (再1秒后)
# 结束时间: 14:30:02
使用aiohttp库实现:
python复制import aiohttp
async def fetch_url(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def main():
urls = [
'https://example.com',
'https://example.org',
'https://example.net'
]
tasks = [fetch_url(url) for url in urls]
results = await asyncio.gather(*tasks)
for url, content in zip(urls, results):
print(f"{url}: {len(content)} bytes")
python复制class AsyncResource:
async def __aenter__(self):
print("资源初始化")
return self
async def __aexit__(self, exc_type, exc, tb):
print("资源清理")
async def use_resource():
async with AsyncResource() as resource:
print("使用资源中")
python复制class AsyncCounter:
def __init__(self, stop):
self.current = 0
self.stop = stop
def __aiter__(self):
return self
async def __anext__(self):
if self.current >= self.stop:
raise StopAsyncIteration
await asyncio.sleep(0.5)
self.current += 1
return self.current - 1
async def main():
async for number in AsyncCounter(3):
print(number)
python复制async def async_gen():
for i in range(3):
await asyncio.sleep(0.5)
yield i
async def main():
async for item in async_gen():
print(item)
警告:在协程中混用同步阻塞代码会破坏事件循环!
错误示例:
python复制async def bad_example():
time.sleep(1) # 同步阻塞!
正确做法:
python复制async def good_example():
await asyncio.sleep(1) # 异步非阻塞
python复制async def cancellable_task():
try:
while True:
print("任务运行中...")
await asyncio.sleep(1)
except asyncio.CancelledError:
print("任务被取消!")
raise
async def main():
task = asyncio.create_task(cancellable_task())
await asyncio.sleep(2.5)
task.cancel()
try:
await task
except asyncio.CancelledError:
print("主函数捕获到取消异常")
python复制async def long_running_task():
await asyncio.sleep(10)
return "完成"
async def main():
try:
result = await asyncio.wait_for(long_running_task(), timeout=3.0)
except asyncio.TimeoutError:
print("任务超时!")
python复制asyncio.run(main(), debug=True)
python复制tasks = asyncio.all_tasks()
for task in tasks:
print(task)
python复制import logging
logging.basicConfig(level=logging.DEBUG)
对于数据库或HTTP连接,应使用连接池:
python复制from aiohttp import ClientSession
async def fetch_all(urls):
async with ClientSession() as session:
tasks = []
for url in urls:
task = asyncio.create_task(fetch_one(session, url))
tasks.append(task)
return await asyncio.gather(*tasks)
使用信号量控制最大并发数:
python复制async def worker(semaphore, url):
async with semaphore:
return await fetch_url(url)
async def main():
semaphore = asyncio.Semaphore(10) # 最大10个并发
tasks = [worker(semaphore, url) for url in urls]
return await asyncio.gather(*tasks)
python复制async def fetch_with_retry(url, max_retries=3):
for attempt in range(max_retries):
try:
return await fetch_url(url)
except Exception as e:
if attempt == max_retries - 1:
raise
await asyncio.sleep(1 * (attempt + 1))
python复制import logging
from logging.handlers import RotatingFileHandler
def setup_logging():
logger = logging.getLogger('async_app')
logger.setLevel(logging.DEBUG)
handler = RotatingFileHandler(
'app.log', maxBytes=1024*1024, backupCount=5
)
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
使用FastAPI框架:
python复制from fastapi import FastAPI
import asyncio
app = FastAPI()
@app.get("/")
async def read_root():
await asyncio.sleep(0.1)
return {"message": "Hello World"}
@app.get("/items/{item_id}")
async def read_item(item_id: int):
# 模拟数据库查询
await asyncio.sleep(0.5)
return {"item_id": item_id}
使用asyncpg连接PostgreSQL:
python复制import asyncpg
async def get_db_connection():
return await asyncpg.connect(
user='user',
password='password',
database='database',
host='localhost'
)
async def fetch_users():
conn = await get_db_connection()
try:
return await conn.fetch('SELECT * FROM users')
finally:
await conn.close()
使用aioredis:
python复制import aioredis
async def process_messages():
redis = await aioredis.create_redis('redis://localhost')
try:
while True:
message = await redis.brpop('queue', timeout=1)
if message:
print(f"处理消息: {message[1].decode()}")
finally:
redis.close()
await redis.wait_closed()
python复制import pytest
from myapp import async_function
@pytest.mark.asyncio
async def test_async_function():
result = await async_function()
assert result == expected_value
python复制from unittest.mock import AsyncMock
@pytest.mark.asyncio
async def test_with_mock():
mock = AsyncMock(return_value=42)
result = await mock()
assert result == 42
python复制from fastapi.testclient import TestClient
from myapp import app
@pytest.mark.asyncio
async def test_read_item():
async with TestClient(app) as client:
response = await client.get("/items/42")
assert response.status_code == 200
assert response.json() == {"item_id": 42}
python复制from asyncio import Queue
class PubSub:
def __init__(self):
self.subscribers = set()
async def publish(self, message):
for queue in self.subscribers:
await queue.put(message)
async def subscribe(self):
queue = Queue()
self.subscribers.add(queue)
try:
while True:
yield await queue.get()
finally:
self.subscribers.remove(queue)
python复制async def worker(queue):
while True:
task = await queue.get()
try:
await process_task(task)
except Exception as e:
print(f"任务失败: {e}")
finally:
queue.task_done()
async def main():
queue = asyncio.Queue(maxsize=10)
workers = [asyncio.create_task(worker(queue)) for _ in range(3)]
for task in generate_tasks():
await queue.put(task)
await queue.join()
for w in workers:
w.cancel()
python复制from functools import wraps
def async_cache(maxsize=128):
cache = {}
def decorator(func):
@wraps(func)
async def wrapper(*args):
key = args
if key not in cache:
if len(cache) >= maxsize:
cache.popitem()
cache[key] = await func(*args)
return cache[key]
return wrapper
return decorator
@async_cache()
async def expensive_operation(x):
await asyncio.sleep(1)
return x * x
| 类别 | 推荐库 | 特点 |
|---|---|---|
| HTTP客户端 | aiohttp, httpx | 支持HTTP/2, 连接池管理 |
| 数据库 | asyncpg, databases | 原生异步驱动, 连接池 |
| ORM | tortoise-orm | Django风格API, 支持迁移 |
| 消息队列 | aiokafka, aio-pika | Kafka/RabbitMQ异步客户端 |
| 测试 | pytest-asyncio | 异步测试支持 |
| 任务队列 | arq | Redis支持的异步任务队列 |
使用cProfile分析协程:
python复制import cProfile
import asyncio
async def my_task():
await asyncio.sleep(1)
def profile_coroutine():
loop = asyncio.get_event_loop()
pr = cProfile.Profile()
pr.enable()
loop.run_until_complete(my_task())
pr.disable()
pr.print_stats(sort='time')
使用pyinstrument进行异步分析:
python复制from pyinstrument import Profiler
async def main():
profiler = Profiler(async_mode='enabled')
profiler.start()
await my_async_function()
profiler.stop()
print(profiler.output_text(unicode=True, color=True))
python复制import concurrent.futures
def blocking_io():
# 同步阻塞操作
time.sleep(1)
return "完成"
async def main():
loop = asyncio.get_running_loop()
with concurrent.futures.ThreadPoolExecutor() as pool:
result = await loop.run_in_executor(
pool, blocking_io
)
print(result)
python复制def cpu_bound(number):
return sum(i * i for i in range(number))
async def main():
loop = asyncio.get_running_loop()
with concurrent.futures.ProcessPoolExecutor() as pool:
result = await loop.run_in_executor(
pool, cpu_bound, 10_000_000
)
print(result)
python复制import multiprocessing
async def worker(name):
print(f"{name} 开始工作")
await asyncio.sleep(1)
print(f"{name} 工作完成")
def start_worker(name):
asyncio.run(worker(name))
if __name__ == '__main__':
processes = []
for i in range(3):
p = multiprocessing.Process(
target=start_worker,
args=(f"Worker-{i}",)
)
processes.append(p)
p.start()
for p in processes:
p.join()
python复制from typing import Awaitable, Callable, TypeVar
from functools import wraps
T = TypeVar('T')
def async_inject(**dependencies):
def decorator(func: Callable[..., Awaitable[T]]):
@wraps(func)
async def wrapper(*args, **kwargs):
resolved = {}
for name, provider in dependencies.items():
resolved[name] = await provider()
return await func(*args, **kwargs, **resolved)
return wrapper
return decorator
async def get_db():
return DatabaseConnection()
@async_inject(db=get_db)
async def business_logic(user_id: int, db: DatabaseConnection):
return await db.get_user(user_id)
python复制class CircuitBreaker:
def __init__(self, max_failures=3, reset_timeout=30):
self.max_failures = max_failures
self.reset_timeout = reset_timeout
self.failures = 0
self.last_failure = None
self.state = "closed"
async def call(self, func, *args, **kwargs):
if self.state == "open":
if time.time() - self.last_failure > self.reset_timeout:
self.state = "half-open"
else:
raise CircuitOpenError()
try:
result = await func(*args, **kwargs)
if self.state == "half-open":
self.state = "closed"
self.failures = 0
return result
except Exception as e:
self.failures += 1
self.last_failure = time.time()
if self.failures >= self.max_failures:
self.state = "open"
raise
典型组件:
示例服务通信:
python复制async def process_order(order):
async with aiohttp.ClientSession() as session:
# 调用库存服务
async with session.post(
"http://inventory/check",
json={"items": order.items}
) as resp:
stock = await resp.json()
# 调用支付服务
async with session.post(
"http://payment/charge",
json={"amount": order.total}
) as resp:
payment = await resp.json()
# 调用物流服务
async with session.post(
"http://shipping/schedule",
json={"address": order.address}
) as resp:
shipping = await resp.json()
return {
"stock": stock,
"payment": payment,
"shipping": shipping
}
使用JWT的异步验证:
python复制from jose import jwt
from jose.exceptions import JWTError
async def verify_token(token: str):
try:
payload = jwt.decode(
token,
"secret",
algorithms=["HS256"]
)
return payload
except JWTError:
raise HTTPException(
status_code=401,
detail="无效令牌"
)
使用Pydantic进行异步验证:
python复制from pydantic import BaseModel, validator
class UserInput(BaseModel):
username: str
password: str
@validator('password')
def validate_password(cls, v):
if len(v) < 8:
raise ValueError("密码至少8个字符")
return v
async def create_user(user_input: UserInput):
# 业务逻辑
pass
python复制from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
@app.get("/")
@limiter.limit("5/minute")
async def limited_route(request: Request):
return {"message": "受限制的路由"}
python复制import uvloop
# 使用更快的uvloop实现
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
# 自定义事件循环参数
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# 配置参数
loop.slow_callback_duration = 0.1 # 警告慢回调(秒)
__slots__减少异步类内存占用将CPU密集型任务分流到进程池:
python复制async def process_data(data):
loop = asyncio.get_running_loop()
# 将CPU密集型计算分流到进程
result = await loop.run_in_executor(
None, # 使用默认进程池
cpu_intensive_processing,
data
)
# 继续异步处理
await store_result(result)
code复制my_async_project/
├── app/ # 主应用包
│ ├── __init__.py
│ ├── main.py # 应用入口
│ ├── core/ # 核心功能
│ │ ├── config.py # 配置管理
│ │ ├── logging.py # 日志配置
│ │ └── exceptions.py # 自定义异常
│ ├── api/ # API端点
│ │ ├── routers/ # 路由模块
│ │ └── dependencies/ # 依赖项
│ ├── services/ # 业务服务
│ │ ├── database.py # 数据库访问
│ │ └── external.py # 外部服务
│ └── models/ # 数据模型
│ ├── schemas.py # Pydantic模型
│ └── orm.py # ORM模型
├── tests/ # 测试代码
│ ├── unit/ # 单元测试
│ └── integration/ # 集成测试
├── scripts/ # 实用脚本
│ ├── migrate.py # 数据库迁移
│ └── worker.py # 后台工作进程
└── requirements/ # 依赖管理
├── base.txt # 基础依赖
├── dev.txt # 开发依赖
└── prod.txt # 生产依赖
使用Poetry管理异步项目依赖:
toml复制[tool.poetry]
name = "my-async-project"
version = "0.1.0"
[tool.poetry.dependencies]
python = "^3.8"
fastapi = "^0.68.0"
uvicorn = "^0.15.0"
aiohttp = "^3.8.1"
asyncpg = "^0.24.0"
aioredis = "^2.0.0"
[tool.poetry.dev-dependencies]
pytest = "^6.2.4"
pytest-asyncio = "^0.16.0"
httpx = "^0.19.0"
python复制from fastapi import FastAPI
from .core.config import settings
from .api import routers
def create_app():
app = FastAPI(
title=settings.PROJECT_NAME,
description=settings.PROJECT_DESCRIPTION,
version=settings.VERSION
)
# 设置路由
app.include_router(routers.api_router)
# 添加事件处理器
@app.on_event("startup")
async def startup():
await connect_database()
@app.on_event("shutdown")
async def shutdown():
await close_database()
return app
Python 3.11+引入的TaskGroup:
python复制async def main():
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(fetch_data(1))
task2 = tg.create_task(fetch_data(2))
# 所有任务完成后继续
print(f"结果: {task1.result()}, {task2.result()}")
Python 3.10+对异步生成器的增强:
python复制async def stream_data():
try:
for i in range(10):
yield i
await asyncio.sleep(0.1)
finally:
print("清理资源")
async def main():
async for data in stream_data():
print(data)
asyncio.run()包装旧代码| 挑战 | 解决方案 |
|---|---|
| 全局状态 | 使用上下文变量或依赖注入 |
| 线程本地存储 | 替换为异步上下文变量 |
| 阻塞库依赖 | 寻找异步替代品或使用线程池 |
| 测试框架兼容性 | 使用pytest-asyncio等适配工具 |
| 监控工具集成 | 确保监控客户端支持异步 |
原始同步代码:
python复制def fetch_user_sync(user_id):
response = requests.get(f"https://api.example.com/users/{user_id}")
return response.json()
迁移后异步版本:
python复制async def fetch_user_async(user_id):
async with aiohttp.ClientSession() as session:
async with session.get(f"https://api.example.com/users/{user_id}") as response:
return await response.json()
过渡适配器:
python复制async def fetch_user(user_id):
try:
return await fetch_user_async(user_id)
except Exception:
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, fetch_user_sync, user_id)
asyncio调试模式:
python复制asyncio.run(main(), debug=True)
日志记录协程信息:
python复制def debug_coro(coro):
async def wrapper(*args, **kwargs):
print(f"开始执行: {coro.__name__}")
try:
return await coro(*args, **kwargs)
finally:
print(f"完成执行: {coro.__name__}")
return wrapper
可视化追踪:
python复制from asyncio import Task
def print_tasks():
for task in Task.all_tasks():
print(f"任务: {task.get_name()}, 状态: {task._state}")
使用异步分析器:
python复制import asyncio
from pyinstrument import Profiler
async def main():
profiler = Profiler(async_mode='enabled')
profiler.start()
# 你的异步代码
profiler.stop()
print(profiler.output_text(unicode=True, color=True))
asyncio.run(main())
在实际项目中应用asyncio时,有几个关键点值得特别注意:
数据库连接池大小 - 通常设置为(核心数 * 2) + 磁盘数是个不错的起点。我曾在高负载系统中发现连接池过小导致性能瓶颈的问题。
超时设置 - 每个网络请求都应该设置合理的超时。曾经因为一个外部API没有超时设置,导致整个服务挂起。
任务取消处理 - 正确处理CancelledError非常重要。有次忘记在数据库操作中处理取消,导致事务泄漏。
背压管理 - 当生产者快于消费者时,需要使用有界队列或其他背压策略。一次内存溢出事故教会我这个教训。
测试覆盖率 - 异步代码的路径比同步代码更复杂,需要更高的测试覆盖率。建议至少达到85%以上。
一个小技巧:在开发阶段可以使用PYTHONASYNCIODEBUG=1环境变量来启用更详细的调试信息,这对定位异步问题非常有帮助。