Skip to content

第 11 章 · 异步客户端 AsyncClient

本章目标:掌握 httpx.AsyncClient 的用法,理解 asyncio 并发模型,写出高效的异步 HTTP 客户端。

11.1 异步 HTTP 的价值

同步代码在 I/O 等待时阻塞整个线程。异步 I/O 允许单线程同时处理数千个连接:

python
import asyncio
import httpx

async def fetch_repo(client: httpx.AsyncClient, owner: str, repo: str):
    """获取单个仓库信息"""
    resp = await client.get(f'/repos/{owner}/{repo}')
    resp.raise_for_status()
    data = resp.json()
    return {
        'name': data['full_name'],
        'stars': data['stargazers_count']
    }

async def main():
    async with httpx.AsyncClient(base_url="https://api.github.com") as client:
        # 并发获取多个仓库
        tasks = [
            fetch_repo(client, 'encoding', 'httpx'),
            fetch_repo(client, 'psf', 'requests'),
            fetch_repo(client, 'pytest-dev', 'pytest')
        ]
        results = await asyncio.gather(*tasks)
        for r in results:
            print(f"{r['name']}: {r['stars']} ⭐")

asyncio.run(main())

11.2 异步 vs 并发对比

python
import time
import httpx

# 同步串行:10 个请求 × 300ms = 3000ms
start = time.time()
for i in range(10):
    httpx.get('https://httpbin.org/delay/0.3')
print(f'同步串行: {time.time() - start:.1f}s')

# 异步并发:10 个请求 ~ 300ms +  overhead
async def async_benchmark():
    async with httpx.AsyncClient() as client:
        tasks = [client.get('https://httpbin.org/delay/0.3') for _ in range(10)]
        await asyncio.gather(*tasks)

start = time.time()
asyncio.run(async_benchmark())
print(f'异步并发: {time.time() - start:.1f}s')

11.3 异步上下文管理器

python
import httpx
import asyncio

async def process_data():
    async with httpx.AsyncClient() as client:
        # 客户端在整个 with 块内共享
        resp1 = await client.get('/api/users')
        resp2 = await client.post('/api/data', json={'key': 'value'})
        
        # 自动关闭所有连接
    # 退出 with 后连接已释放

11.4 错误处理

python
import httpx
import asyncio

async def safe_request(url: str):
    try:
        async with httpx.AsyncClient() as client:
            resp = await client.get(url, timeout=10.0)
            resp.raise_for_status()
            return resp.json()
    except httpx.HTTPStatusError as e:
        print(f'HTTP 错误 {e.response.status_code}: {e.request.url}')
    except httpx.ConnectError as e:
        print(f'连接失败: {e}')
    except httpx.TimeoutException:
        print('请求超时')
    return None

11.5 本章小结

  • asyncio.gather() 并发执行多个异步请求,显著降低总耗时;
  • 始终用 async with httpx.AsyncClient() 管理客户端生命周期;
  • 区分 HTTPStatusError(4xx/5xx)、ConnectError(网络层)、TimeoutException(超时)。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. asyncio.gather() 的主要作用是?

2. httpx.AsyncClient 必须用 async with 管理吗?

3. HTTP 404 在 httpx 中会抛出什么异常?

4. 异步 HTTP 客户端适合哪些场景?

🛠️ 动手实践

  1. 用 asyncio.gather 并发获取 5 个 GitHub 用户信息,对比串行实现耗时。
  2. 实现一个带重试的异步请求函数,失败后指数退避重试。
  3. 编写异步健康检查器,并发检测多个 URL 的可用性。

下一章:并发请求与并发控制