python非阻塞用async 和 await来替换gather函数

不用 gather,只用 async/await 实现并发的核心原理

一、问题本质:如何在不使用 gather 的情况下并发执行多个协程?

核心思路:

  1. 手动创建并启动任务(asyncio.create_task()
  2. 使用 await 分别等待每个任务完成,但需确保任务在等待前已全部启动
二、示例代码:手动管理多个协程的并发执行
import asyncio
import time

async def task(name, delay):
    print(f"【{name}】开始执行,等待 {delay} 秒")
    await asyncio.sleep(delay)  # 模拟IO操作,让出控制权给事件循环
    print(f"【{name}】执行完毕")
    return f"{name} 的结果"

async def main():
    print("【main】开始执行")
    
    # 步骤1:创建任务(但不等待,让它们在后台运行)
    task1 = asyncio.create_task(task("任务1", 2))  # 创建任务1,延迟2秒
    task2 = asyncio.create_task(task("任务2", 1))  # 创建任务2,延迟1秒
    
    print("【main】两个任务已创建,开始等待它们完成")
    
    # 步骤2:依次等待任务完成(顺序不影响并发执行)
    print("【main】开始等待 任务1 完成")
    result1 = await task1  # 等待任务1完成(但任务1和任务2实际在并发执行)
    print(f"【main】已获取 任务1 的结果: {result1}")
    
    print("【main】开始等待 任务2 完成")
    result2 = await task2  # 等待任务2完成(此时任务2可能已完成,无需等待)
    print(f"【main】已获取 任务2 的结果: {result2}")
    
    print("【main】所有任务已完成")
    return [result1, result2]

# 运行主函数
start_time = time.time()
print("【程序入口】开始运行")
results = asyncio.run(main())
end_time = time.time()
print(f"【程序入口】所有任务完成,总耗时: {end_time - start_time:.2f} 秒")
print(f"【程序入口】最终结果: {results}")
三、执行流程解析(关键!)
  1. asyncio.create_task() 的作用

    • 将协程包装为 Task 对象,并提交给事件循环
    • 任务会在下一个事件循环周期开始执行(但不会阻塞当前协程)
  2. 执行时序图

时间线 →
0s:  main() 开始执行
    |- 创建任务1(延迟2秒)
    |- 创建任务2(延迟1秒)
    |- 开始等待任务1

0s:  任务1开始执行 → 等待2秒 → 让出控制权
0s:  任务2开始执行 → 等待1秒 → 让出控制权
    ↓
事件循环此时没有可执行的协程,进入等待状态...

1s:  任务2的1秒等待结束 → 继续执行 → 完成
    ↓
事件循环继续等待其他IO事件...

2s:  任务1的2秒等待结束 → 继续执行 → 完成
    ↓
main() 恢复执行,获取任务1的结果(实际已完成)
main() 继续等待任务2的结果(实际已完成,立即获取)
  1. 关键点
    • 虽然 await task1await task2 是串行的,但任务1和任务2在后台是并发执行的
    • 总耗时取决于最长的任务(2秒),而非任务的累加时间(3秒)
四、输出验证(运行代码的实际输出)
【程序入口】开始运行
【main】开始执行
【main】两个任务已创建,开始等待它们完成
【main】开始等待 任务1 完成
【任务1】开始执行,等待 2 秒
【任务2】开始执行,等待 1 秒
【任务2】执行完毕
【任务1】执行完毕
【main】已获取 任务1 的结果: 任务1 的结果
【main】开始等待 任务2 完成
【main】已获取 任务2 的结果: 任务2 的结果
【main】所有任务已完成
【程序入口】所有任务完成,总耗时: 2.00 秒
【程序入口】最终结果: ['任务1 的结果', '任务2 的结果']
五、asyncio.gather 的等效实现

其实 asyncio.gather 本质上就是帮你封装了上述手动管理任务的过程:

# 使用 gather 的写法
results = await asyncio.gather(task1, task2)

# 等效于手动写法
await task1
await task2
results = [task1.result(), task2.result()]

区别在于:

  1. gather 会并行等待所有任务,而手动 await 是串行等待(但任务执行仍是并发的)
  2. gather 会自动处理异常聚合和结果排序
  3. 手动写法更灵活,可以在等待中间插入其他逻辑
六、终极总结:为什么这样能并发?

核心在于理解 asyncio.create_task() 的作用:

  • 它将协程提交给事件循环,让事件循环在合适的时机执行这个协程
  • 一旦任务被创建,事件循环会在当前协程(这里是 main())遇到 await 暂停时,立即调度其他就绪的任务
  • 因此,只要在 await 之前创建所有任务,它们就会在后台并发执行

通过这种方式,即使不使用 gather,也能实现多个协程的并发执行。

你可能感兴趣的:(python非阻塞用async 和 await来替换gather函数)