httpx原生支持async/await,而requests完全同步阻塞;混用requests于async函数会卡死事件循环,httpx.asyncclient才真正协程友好,需配合async with和asyncio.gather实现高效并发。

requests 本身不支持异步请求,httpx 原生支持 async/await,这是两者在异步处理上的根本分界线。
requests 无法直接写 await client.get()
requests 的所有方法(如 requests.get()、requests.post())都是同步阻塞的。你在 async 函数里直接调用它们,会阻塞整个事件循环——不是“慢一点”,而是让其他协程完全卡住。
常见错误现象:
- 代码写了
async def main(),但里面混用requests.get(),结果并发没效果,耗时接近串行 - 误以为加了
async就自动异步,实际只是语法合法,运行时仍单线程排队等待
若硬要“模拟”并发,只能靠 ThreadPoolExecutor 提交任务,本质是多线程,受 GIL 限制,资源开销大,且无法与 asyncio 生态(如 FastAPI、Starlette)原生集成。
httpx.AsyncClient 是真正协程友好的客户端
httpx.AsyncClient 专为 asyncio 设计,所有 I/O 操作都基于 await,不抢占线程,等待响应时自动让出控制权。
使用要点:
- 必须在
async函数中使用,且用await调用.get()、.post()等方法 -
AsyncClient实例不能跨await复用,推荐用async with保证连接池和资源释放 - 并发多个请求时,直接用
asyncio.gather(),无需手动管理线程或回调
示例片段:
import httpx
import asyncio
<p>async def fetch(url):
async with httpx.AsyncClient() as client:
response = await client.get(url)
return response.status_code</p><p>async def main():
tasks = [fetch("<a href="https://www.php.cn/link/5f69e19efaba426d62faeab93c308f5c">https://www.php.cn/link/5f69e19efaba426d62faeab93c308f5c</a>") for _ in range(5)]
results = await asyncio.gather(*tasks)
print(results) # 输出 [200, 200, 200, 200, 200],总耗时约 1 秒左右
</p>
同步 API 一致,但异步行为不可互换
httpx.get() 和 httpx.post() 这类顶层函数是同步的,行为与 requests.get() 几乎完全一致——方便迁移旧代码。但它们和 AsyncClient 之间没有共享状态,也不能混用 await。
容易踩的坑:
- 以为
await httpx.get(...)合法 —— 实际会报TypeError: object Response can't be used in 'await' expression - 把
httpx.Client()(同步)误当httpx.AsyncClient()使用,导致RuntimeWarning: coroutine 'AsyncClient.__aenter__' was never awaited - 在 FastAPI 路由中用了
requests,拖慢整个异步服务吞吐量,尤其在高并发下响应延迟陡增
HTTP/2 和类型提示是隐性加分项
httpx 默认不启用 HTTP/2,但只要装了 httpx[http2] 并传入 http2=True,就能在支持的服务端复用连接、提升首字节时间;而 requests 完全不支持 HTTP/2。
另外,httpx 全库带完整类型提示,IDE 自动补全更准,mypy 检查更严格——这对中大型项目维护成本影响不小,但初学者常忽略这点。
真正难的不是写对第一行 await,而是理解:异步不是“加个 async 就快”,而是整套执行模型的切换。一旦用了 httpx.AsyncClient,你就得确保上下游(比如数据库驱动、缓存客户端)也兼容协程,否则一处阻塞,全局退化。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











