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

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


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

相关推荐
周末也要写八哥1 天前
返回函数(闭包):让return更“高阶
python
消失的旧时光-19431 天前
C++ 网络服务端主线:从线程池到 Reactor 的完整路线图
开发语言·网络·c++·线程池·并发
疯狂打码的少年1 天前
【Day02 Java转Python】Python的ArrayList: list与tuple的“双面人生
java·python·list
打瞌睡的朱尤1 天前
js复习--考核
开发语言·前端·javascript
wjs20241 天前
SQL SELECT DISTINCT 详解
开发语言
暴力袋鼠哥1 天前
基于 LightGBM 的山东高考智能择校推荐系统设计与实现
python·django·flask
计算机安禾1 天前
【数据结构与算法】第25篇:静态查找(一):顺序查找与折半查找
java·开发语言·数据结构·学习·算法·visual studio code·visual studio
cch89181 天前
易语言与Java对比:中文编程VS跨平台王者
java·开发语言
cookies_s_s1 天前
C++ 模板与泛型编程
linux·服务器·开发语言·c++