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

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


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

相关推荐
全栈弄潮儿2 小时前
Python实战第1期:Python环境搭建与第一个程序
python
奋斗的阿狸_19862 小时前
ESP32-S3 + ES7210 四通道麦克风录音上传方案
c语言·开发语言·fpga开发
心易行者2 小时前
Agent应用+API端点商业化进阶实战:从单体智能体到可付费调用的API全流程
运维·服务器·人工智能·python·apache
weixin199701080162 小时前
《1688图片空间API踩坑:img.upload 与 album.* 的防盗链与CDN缓存问题》(附Python源码)
开发语言·python·缓存
ACP广源盛139246256732 小时前
Type‑C 扩展坞方案选型笔记|国产 DP1.4 转 HDMI2.0 桥接芯片 GSV2201S @ACP评估
c语言·开发语言·笔记·硬件架构·硬件工程·国产芯片
布吉岛的石头2 小时前
Java 程序员第 49 阶段11:Java 接 BERT:用 ONNX Runtime 做句向量推理
java·人工智能·python·深度学习·bert·transformer
第25小时记录2 小时前
数据结构与算法 -第 3 章 常用算法-动态规划
数据结构·python
Dmz丶2 小时前
前端转 AI 100 天 Day 18:pytest 入门——为什么你必须要写测试
python
niucloud-admin2 小时前
JAVA V6 多商户商城 开发文档——JAVA服务端
java·开发语言
用户019027581613 小时前
如何用 Python 计算 DMA 平行线差指标?
python