Python 异步编程完全指南(二):深入 asyncio 核心概念
前言
上一篇我们学习了异步编程的基础知识。本篇将深入 asyncio 的核心概念,帮你建立完整的知识体系。
一、事件循环 (Event Loop)
事件循环是异步编程的心脏,负责调度和执行协程。
1.1 事件循环工作原理
┌────────────────────────────────────────────────────────────────┐
│ 事件循环工作原理 │
├────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 协程 A │ │ 协程 B │ │ 协程 C │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ 任务队列 (Task Queue) │ │
│ └───────────────────────┬───────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ 事件循环 (Event Loop) │ │
│ │ • 检查就绪的任务 │ │
│ │ • 执行任务直到遇到 await │ │
│ │ • 切换到下一个就绪任务 │ │
│ │ • 处理 I/O 事件 │ │
│ └───────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
简单理解:事件循环就像一个调度员,不断检查哪些任务可以执行,执行到需要等待时(遇到 await),就切换去执行其他任务。
1.2 事件循环的基本用法
python
import asyncio
async def main():
print("Hello, asyncio!")
# 方式 1:推荐(Python 3.7+)
asyncio.run(main())
# 方式 2:手动管理(更多控制)
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
loop.run_until_complete(main())
finally:
loop.close()
# 方式 3:获取当前运行的循环(在协程内部使用)
async def inside_coroutine():
loop = asyncio.get_running_loop()
print(f"当前循环: {loop}")
1.3 事件循环的生命周期
python
import asyncio
async def task(name):
print(f"任务 {name} 开始")
await asyncio.sleep(1)
print(f"任务 {name} 结束")
async def main():
# 获取当前事件循环
loop = asyncio.get_running_loop()
print(f"事件循环状态: running={loop.is_running()}")
await task("A")
# asyncio.run() 会:
# 1. 创建新的事件循环
# 2. 运行传入的协程
# 3. 关闭事件循环
asyncio.run(main())
二、协程 (Coroutine)
协程是可以暂停和恢复的函数,是异步编程的基本单元。
2.1 协程的三种状态
创建 ──────► 挂起 ──────► 完成
│ ▲ │
│ │ │
└───────────┘ │
等待 (await) │
▼
返回结果
2.2 协程对象详解
python
import asyncio
async def my_coroutine():
print("协程执行中")
await asyncio.sleep(1)
return "完成"
# 调用协程函数返回协程对象
coro = my_coroutine()
print(f"类型: {type(coro)}") # <class 'coroutine'>
# 协程对象必须被 await 或作为 Task 运行
async def main():
result = await coro
print(f"结果: {result}")
asyncio.run(main())
2.3 协程 vs 普通函数
| 特性 | 普通函数 | 协程函数 |
|---|---|---|
| 定义方式 | def func() |
async def func() |
| 调用结果 | 直接执行,返回结果 | 返回协程对象 |
| 执行方式 | 同步执行 | 需要 await 或事件循环 |
| 可暂停 | 否 | 是(在 await 处) |
三、Task(任务)
Task 是对协程的包装,用于并发执行多个协程。
3.1 创建 Task
python
import asyncio
async def worker(name, delay):
print(f"Worker {name} 开始")
await asyncio.sleep(delay)
print(f"Worker {name} 完成")
return f"result_{name}"
async def main():
# 创建 Task(会立即开始调度)
task1 = asyncio.create_task(worker("A", 2))
task2 = asyncio.create_task(worker("B", 1))
task3 = asyncio.create_task(worker("C", 3))
print("所有任务已创建")
# 等待所有任务完成
result1 = await task1
result2 = await task2
result3 = await task3
print(f"结果: {result1}, {result2}, {result3}")
asyncio.run(main())
输出:
所有任务已创建
Worker A 开始
Worker B 开始
Worker C 开始
Worker B 完成
Worker A 完成
Worker C 完成
结果: result_A, result_B, result_C
3.2 Task 的属性和方法
python
import asyncio
async def my_task():
await asyncio.sleep(2)
return "done"
async def main():
task = asyncio.create_task(my_task(), name="MyTask")
# 任务属性
print(f"任务名称: {task.get_name()}") # MyTask
print(f"是否完成: {task.done()}") # False
print(f"是否取消: {task.cancelled()}") # False
# 等待完成
await task
print(f"是否完成: {task.done()}") # True
print(f"结果: {task.result()}") # done
asyncio.run(main())
3.3 协程执行流程图
┌─────────────────────────────────────────────────────────────────────┐
│ 协程执行流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ async def fetch(): 事件循环调度器 │
│ print("start") ┌─────────────┐ │
│ await sleep(1) ──────────►│ 暂停 fetch │ │
│ print("end") │ 执行其他任务 │ │
│ return data │ sleep完成后 │ │
│ ◄──────────│ 恢复 fetch │ │
│ └─────────────┘ │
│ │
│ 时间线: │
│ ───────────────────────────────────────────────────────────► │
│ │ print │ 其他任务执行 │ print │ │
│ │ "start" │ (1秒等待) │ "end" │ │
│ │
└─────────────────────────────────────────────────────────────────────┘
四、并发控制:gather vs wait vs TaskGroup
4.1 asyncio.gather() - 最常用
python
import asyncio
async def task(id, delay):
await asyncio.sleep(delay)
return f"task_{id}"
async def main():
# 并发执行,按传入顺序返回结果
results = await asyncio.gather(
task(1, 2),
task(2, 1),
task(3, 3),
)
print(results) # ['task_1', 'task_2', 'task_3']
asyncio.run(main())
特点:
- 按传入顺序返回结果(不是完成顺序)
- 任一任务失败会中断所有任务(除非设置
return_exceptions=True)
4.2 异常处理
python
async def risky_task(id):
if id == 2:
raise ValueError(f"任务 {id} 出错!")
await asyncio.sleep(1)
return f"task_{id}"
async def main():
# 不处理异常:一个失败,gather 就抛出异常
# results = await asyncio.gather(
# risky_task(1),
# risky_task(2), # 这个会失败
# risky_task(3),
# )
# 处理异常:异常作为结果返回,不中断其他任务
results = await asyncio.gather(
risky_task(1),
risky_task(2), # 返回 ValueError 对象
risky_task(3),
return_exceptions=True
)
for i, result in enumerate(results, 1):
if isinstance(result, Exception):
print(f"任务 {i} 失败: {result}")
else:
print(f"任务 {i} 成功: {result}")
asyncio.run(main())
4.3 asyncio.wait() - 更多控制
python
import asyncio
async def task(id, delay):
await asyncio.sleep(delay)
return f"task_{id}"
async def main():
tasks = [
asyncio.create_task(task(1, 2)),
asyncio.create_task(task(2, 1)),
asyncio.create_task(task(3, 3)),
]
# 等待第一个完成
done, pending = await asyncio.wait(
tasks,
return_when=asyncio.FIRST_COMPLETED
)
print(f"完成: {len(done)}, 待处理: {len(pending)}")
for task in done:
print(f"结果: {task.result()}")
# 可以选择取消剩余任务
for task in pending:
task.cancel()
asyncio.run(main())
return_when 参数:
FIRST_COMPLETED- 第一个完成时返回FIRST_EXCEPTION- 第一个异常时返回ALL_COMPLETED- 全部完成时返回(默认)
4.4 asyncio.TaskGroup - Python 3.11+ 推荐
python
import asyncio
async def task(id, delay):
await asyncio.sleep(delay)
if id == 2:
raise ValueError(f"任务 {id} 失败")
return f"task_{id}"
async def main():
try:
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(task(1, 1))
task2 = tg.create_task(task(2, 2)) # 这个会失败
task3 = tg.create_task(task(3, 1))
# TaskGroup 自动等待所有任务完成
print(task1.result(), task3.result())
except* ValueError as eg:
# Python 3.11+ 的 ExceptionGroup 处理
for exc in eg.exceptions:
print(f"捕获异常: {exc}")
asyncio.run(main())
TaskGroup 优势:
- 结构化并发,自动管理任务生命周期
- 任何任务失败,自动取消其他任务
- 更清晰的异常处理
4.5 三种方式对比
| 特性 | gather | wait | TaskGroup |
|---|---|---|---|
| Python 版本 | 3.4+ | 3.4+ | 3.11+ |
| 返回格式 | 结果列表 | (done, pending) 集合 | 无直接返回 |
| 异常处理 | return_exceptions | 手动处理 | ExceptionGroup |
| 取消传播 | 手动 | 手动 | 自动 |
| 推荐场景 | 简单并发 | 需要细粒度控制 | 现代 Python |
五、Future 对象
Future 是一个低级的可等待对象,表示异步操作的最终结果。
5.1 Future 基础
python
import asyncio
async def main():
loop = asyncio.get_running_loop()
# 创建 Future
future = loop.create_future()
# 模拟异步设置结果
async def set_result():
await asyncio.sleep(1)
future.set_result("Hello from Future!")
# 启动设置结果的任务
asyncio.create_task(set_result())
# 等待 Future 完成
result = await future
print(result)
asyncio.run(main())
5.2 Task vs Future
┌─────────────────────────────────────────────────────────────┐
│ Task 与 Future 的关系 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Future │
│ ├── 表示异步操作的最终结果 │
│ ├── 低级 API,很少直接使用 │
│ └── 可以手动设置结果 │
│ │
│ Task (继承自 Future) │
│ ├── 包装协程的 Future │
│ ├── 高级 API,常用 │
│ └── 结果由协程返回值自动设置 │
│ │
│ 简单理解:Task = Future + 协程执行器 │
│ │
└─────────────────────────────────────────────────────────────┘
六、本篇小结
本篇我们深入学习了:
- ✅ 事件循环:异步编程的心脏,负责调度协程
- ✅ 协程 :可暂停和恢复的函数,通过
async def定义 - ✅ Task:协程的包装,用于并发执行
- ✅ gather/wait/TaskGroup:三种并发控制方式及其区别
- ✅ Future:低级可等待对象,Task 的基类
核心要点速查
python
# 1. 运行协程
asyncio.run(main())
# 2. 创建任务
task = asyncio.create_task(coroutine())
# 3. 并发执行
results = await asyncio.gather(coro1(), coro2(), coro3())
# 4. 获取当前循环
loop = asyncio.get_running_loop()
下篇预告
在下一篇 实战案例篇 中,我们将通过 4 个完整项目实战:
- 批量下载图片
- 异步 API 客户端
- 实时数据处理管道
- WebSocket 实时通信
如果这篇文章对你有帮助,欢迎点赞、收藏、关注!有问题欢迎评论区讨论。