Python 异步编程完全指南(二):深入 asyncio 核心概念

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 实时通信

系列导航入门篇 → [核心概念篇] → 实战案例篇高级技巧篇避坑指南篇


如果这篇文章对你有帮助,欢迎点赞、收藏、关注!有问题欢迎评论区讨论。

相关推荐
CAACoder2 小时前
CATIA/3DE CAA二次开发-ScrollWindow滚动窗口
开发语言·c++·mfc·滚动窗口
还是大剑师兰特2 小时前
Vue3 页面权限控制实战示例(路由守卫 + 权限判断)
开发语言·前端·javascript
老前端的功夫2 小时前
【Java从入门到入土】06:String的72变:从字符串拼接到底层优化
java·开发语言
程序猿(雷霆之王)2 小时前
C++——AI大模型接入SDK
开发语言·c++
小付爱coding2 小时前
openclaw源码架构深度解析【总体概况】
python·架构·openclaw
又是忙碌的一天2 小时前
Java 面向对象三大特性:封装、继承、多态深度解析
java·前端·python
会编程的土豆2 小时前
【从零学javase 第六天】网络编程(+多线程)
开发语言·网络·php
Yupureki2 小时前
《C++实战项目-高并发内存池》8. 最终性能优化与测试
c语言·开发语言·数据结构·c++·算法·性能优化
隔壁小邓2 小时前
在Java中实现优雅的CQRS架构
java·开发语言·架构