1. 异步编程是什么? #
- 异步指程序在等待 I/O(网络、磁盘、数据库)时不阻塞当前执行流,而是先去做别的任务,等 I/O 完成后再回来继续。
- 单线程里也能同时「挂着」很多请求,因此特别适合高并发、大量外部调用的 API 服务。
- Python 用
async / await 写协程,用标准库 asyncio 调度。
- 业务里常见组合是 FastAPI 路由 +
httpx / aiohttp 异步 HTTP 客户端。
- 不适合用 async 的场景是 CPU 密集计算(图像处理、大循环、复杂加密)。
- CPU 密集任务应交给多进程或线程池;在 async 里硬算只会占满事件循环,反而拖慢所有请求。
|
同步 |
异步 |
| 等待 I/O |
线程卡住 |
让出执行权,先干别的 |
| 典型场景 |
简单脚本、传统 Django 视图 |
高并发 API、批量调外部接口 |
| 写法 |
def + requests |
async def + await |
2. 定义协程函数 #
- 在 Python 中,用
async def 声明一个协程函数。
- 协程函数不是普通同步函数,遇到
await 时可以挂起等待、让出控制权。
- 调用协程函数会得到协程对象,必须用
await 或交给事件循环执行,否则不会自动跑完。
import asyncio
async def fetch_data(name: str, delay: float):
print(f"{name} 开始")
await asyncio.sleep(delay)
print(f"{name} 完成")
return name
3. 等待异步操作完成 #
await 用于挂起当前协程,等待一个异步操作完成。
- 遇到
await 时,Python 把控制权交回事件循环,去运行其它协程。
- 异步操作完成后,再从
await 处继续执行当前协程。
- 常见用法是等待 I/O,例如
await asyncio.sleep(2)(不会阻塞整个进程的主线程逻辑)。
await asyncio.sleep(2)
4. 启动事件循环并运行 #
- asyncio 的协程必须放进事件循环(event loop)中才能执行。
asyncio.run(main()) 会自动创建并启动事件循环,运行 main 协程,结束后关闭循环。
- 脚本入口常用
asyncio.run(main()),业务在 FastAPI 等框架里一般由框架托管事件循环。
import asyncio
async def fetch_data(name: str, delay: float):
print(f"{name} 开始")
await asyncio.sleep(delay)
print(f"{name} 完成")
return name
async def main():
await fetch_data("A", 2)
asyncio.run(main())
5. 多个协程并发 #
asyncio.gather(...) 可并发运行多个协程,并等待它们全部完成。
gather 返回结果列表,顺序与传入协程的顺序一致。
- 高并发 I/O 场景常用此写法;总耗时约等于最慢的那个任务,而不是各任务耗时之和。
- 日常批量外调优先用
gather,比逐个 await 更高效。
import asyncio
async def fetch_data(name: str, delay: float):
print(f"{name} 开始")
await asyncio.sleep(delay)
print(f"{name} 完成")
return name
async def main():
results = await asyncio.gather(
fetch_data("A", 2),
fetch_data("B", 1),
)
print(results)
asyncio.run(main())
6. 提前调度、稍后再 await #
- 需要提前把任务丢进事件循环、稍后再
await 结果时,用 asyncio.create_task(coro)。
create_task 常见于生产消费、后台监视等需要管理任务生命周期的场景。
- 与
gather 的区别:gather 通常一次性并发并等待全部完成;create_task 可随时创建、单独 await 或取消。
import asyncio
async def fetch_data(name: str, delay: float):
print(f"{name} 开始")
await asyncio.sleep(delay)
print(f"{name} 完成")
return name
async def main():
task = asyncio.create_task(fetch_data("A", 2))
result = await task
print(result)
asyncio.run(main())
7. 并发请求外部 API #
- 真实项目里异步最常见的用途是同时请求多个 URL(聚合第三方数据、健康检查、批量拉配置)。
- 应使用
httpx.AsyncClient(或 aiohttp),在 async with 里复用同一客户端,连接池由库管理。
- 比每次请求都新建客户端更高效,也避免连接泄漏。
- 在
async def 里调用同步 requests.get() 是典型错误:会阻塞整个事件循环,其它协程全部卡住。
- 需要第三方库时优先选带 async 支持的版本;没有则用
run_in_executor 把阻塞调用丢进线程池(了解即可)。
import asyncio
import httpx
async def fetch_status(client: httpx.AsyncClient, url: str) -> int:
resp = await client.get(url, timeout=10.0)
return resp.status_code
async def main():
urls = [
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/2",
]
async with httpx.AsyncClient() as client:
codes = await asyncio.gather(*[fetch_status(client, u) for u in urls])
print(codes)
asyncio.run(main())
安装:pip install httpx 或 uv add httpx。
8. 异常与超时 #
- 线上异步代码必须考虑超时和部分失败:某个 URL 慢或返回 500,不应拖死整批
gather。
httpx 的 timeout 参数限制单次请求最长等待时间。
try/except 捕获 HTTPError 等与同步写法类似。
gather(..., return_exceptions=True) 表示某个协程失败时,结果列表对应位置是异常对象,其余任务照常完成。
- 对爬虫、批量调第三方接口,可用
asyncio.Semaphore(n) 限制同时进行的请求数,避免占满连接或打挂对方服务。
import asyncio
import httpx
async def safe_fetch(client, url):
try:
r = await client.get(url, timeout=5.0)
r.raise_for_status()
return r.text
except httpx.HTTPError as e:
return f"失败: {e}"
async def main():
urls = [
"https://www.example.com",
"https://www.nonexistentwebsite123456789.com",
"https://httpbin.org/get"
]
async with httpx.AsyncClient() as client:
tasks = [safe_fetch(client, url) for url in urls]
results = await asyncio.gather(*tasks, return_exceptions=True)
for i, result in enumerate(results):
print(f"URL {i}: {result}")
asyncio.run(main())
9. 项目开发要点 #
- 落地异步项目时,先自检:是否真是 I/O 瓶颈、链路是否全程 async、外调是否有超时与并发上限。
- I/O 密集才上 async;算力密集用进程/线程,别用 async 假装加速 CPU。
- 一条链路到底:async 视图 → async HTTP/DB 客户端,中间不要夹同步阻塞。
- 批量外调用
gather + httpx.AsyncClient,注意超时与 return_exceptions。
- 错误与日志:
try/except 与同步类似,见异常章节。
- 调试:先缩小为单个
await 复现,再查是否误用阻塞库。
10. 总结 #
- 本章核心:在 I/O 等待时让出执行权,用
async/await 表达协程逻辑。
- 用
asyncio 与异步 HTTP 客户端并发多路请求。
- 不要在 async 里做阻塞或 CPU 重活(同步
requests、time.sleep、大段计算)。
- 下表可作速查。
| 概念 |
要点 |
async / await |
定义与等待协程 |
asyncio.run |
程序入口 |
gather |
并发多个 I/O |
| 异步 HTTP |
httpx + async with Client |
| 禁忌 |
在 async 里 sleep / 同步 requests |