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

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


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

相关推荐
Wang's Blog3 小时前
Go-Zero项目开发4: 用户服务搜索、详情与统一错误处理
开发语言·golang
我星期八休息4 小时前
网络编程—应用层HTTP协议
linux·运维·开发语言·前端·网络·网络协议·http
梅雅达编程笔记4 小时前
零基础学 Python 第14章 | 模块、包与第三方库
开发语言·python·django·numpy·pandas
Mr__Miss4 小时前
Java泛型完全指南:从入门到精通
java·开发语言·python
赵庆明老师4 小时前
Vben精讲:14-Vben远程加载语言包
开发语言·vben
嘿丨嘿5 小时前
VLA 入门(六):VLA 如何进行强化学习后训练?
人工智能·python·深度学习·机器人
hehelm5 小时前
AI大模型接入SDK—通用模块设计
linux·开发语言·c++
祉猷并茂,雯华若锦5 小时前
Appium 3.x实战:获取元素属性全解析
python·appium·自动化
宠友信息6 小时前
消息序号如何保证即时通讯源码聊天记录稳定加载
java·spring boot·redis·python·mysql·uni-app
小柯南敲键盘6 小时前
批量图片翻译与视频字幕一站式解决高效跨境电商沟通难题
大数据·人工智能·python·音视频