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

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


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

相关推荐
qq_392690668 小时前
如何正确解析含 HTML 实体的 XML 字符串并渲染为 HTML 表格
jvm·数据库·python
qq_414256578 小时前
SQL如何处理时间序列缺失值_利用窗口函数进行前后值填充
jvm·数据库·python
2301_803875619 小时前
CSS如何制作导航栏平滑移动_使用transition与left属性
jvm·数据库·python
2501_9333295513 小时前
媒介宣发技术实践:Infoseek舆情系统的AI中台架构与应用解析
开发语言·人工智能·架构·数据库开发
[J] 一坚14 小时前
嵌入式高手C
c语言·开发语言·stm32·单片机·mcu·51单片机·iot
odoo中国14 小时前
Odoo 19技术教程 : 如何在 Odoo 19 中创建 Many2one 组件
开发语言·odoo·odoo19·odoo技术·many2one
逻辑驱动的ken14 小时前
Java高频面试考点场景题14
java·开发语言·深度学习·面试·职场和发展·求职招聘·春招
茅盾体14 小时前
汽车零件订单自动同步系统方案
python
2401_8836002514 小时前
golang如何理解weak pointer弱引用_golang weak pointer弱引用总结
jvm·数据库·python
FreakStudio14 小时前
和做工厂系统的印尼老哥,复刻了一套属于 MicroPython 的包管理系统
python·单片机·嵌入式·大学生·面向对象·并行计算·电子diy·电子计算机