【asyncio】asyncio.gather() 函数:并发运行多个异步任务(协程)并收集它们的返回值

在 Python 中,asyncio.gather()asyncio 模块提供的一个关键函数,用于并发运行多个异步任务(协程)并收集它们的返回值。它是异步编程中常用的工具,适合需要同时执行多个独立 I/O 操作(如网络请求、文件读写)的场景。asyncio.gather() 提高了代码效率,同时保持异步事件循环的非阻塞特性。

以下是对 asyncio.gather() 的详细介绍,包括其定义、用法、示例、应用场景、最佳实践和注意事项。内容基于 Python 官方文档(Python 3.9+)和实际使用经验。


1. asyncio.gather() 的定义和原理

1.1 定义

asyncio.gather(*aws, return_exceptions=False) 是一个异步函数,用于并发运行多个可等待对象(awaitable objects,如协程、任务或 Future),并在所有任务完成后返回它们的执行结果。

  • 参数

    • *aws:可变数量的可等待对象(通常是协程或 asyncio.Task)。
    • return_exceptions:布尔值,控制异常处理方式:
      • False(默认):任意任务抛出异常,整个 gather 立即失败,抛出该异常。
      • True:异常作为结果返回,不中断其他任务。
  • 返回值:一个 list,按输入顺序包含每个可等待对象的结果(或异常对象,如果 return_exceptions=True)。

1.2 原理
  • 并发执行asyncio.gather() 将所有可等待对象调度到事件循环,允许它们并发运行(基于事件循环的非阻塞机制)。
  • 结果收集:等待所有任务完成(或其中一个失败),然后按输入顺序返回结果。
  • 异常处理:根据 return_exceptions 参数,决定是否将异常作为结果返回或抛出。
1.3 与其他并发工具的对比
  • asyncio.wait():更低级,返回完成和未完成任务的集合,灵活但复杂。
  • asyncio.as_completed():按完成顺序迭代结果,适合处理实时结果。
  • asyncio.gather():简单、高效,适合需要所有结果的场景。

2. asyncio.gather() 的基本用法

2.1 基本语法
import asyncio

async def my_coroutine():
    return 42

async def main():
    results = await asyncio.gather(my_coroutine(), my_coroutine())
    print(results)  # 输出: [42, 42]

asyncio.run(main())
2.2 并发运行多个协程

asyncio.gather() 常用于并发执行多个耗时任务(如网络请求)。

示例

import asyncio

async def task(name, delay):
    await asyncio.sleep(delay)
    return f"{name} completed after {delay}s"

async def main():
    tasks = [
        task("A", 2),
        task("B", 1),
        task("C", 3)
    ]
    results = await asyncio.gather(*tasks)
    print(results)

asyncio.run(main())

输出

['A completed after 2s', 'B completed after 1s', 'C completed after 3s']
  • 说明:所有任务并发运行,总耗时约为最长任务(3 秒),结果按输入顺序返回。
2.3 处理异常

设置 return_exceptions=True 捕获异常。

示例

import asyncio

async def success():
    await asyncio.sleep(1)
    return "Success"

async def fail():
    await asyncio.sleep(0.5)
    raise ValueError("Failed")

async def main():
    results = await asyncio.gather(success(), fail(), return_exceptions=True)
    print(results)

asyncio.run(main())

输出

['Success', ValueError('Failed')]
  • 说明:异常作为结果返回,未中断其他任务。
2.4 结合 asyncio.create_task

将协程包装为任务,提前调度。

示例

import asyncio

async def task(name):
    await asyncio.sleep(1)
    return name

async def main():
    tasks = [asyncio.create_task(task(name)) for name in ["A", "B"]]
    results = await asyncio.gather(*tasks)
    print(results)

asyncio.run(main())

输出

['A', 'B']

3. asyncio.gather() 的核心特性

3.1 并发性
  • gather 调度所有任务并发运行,利用事件循环的非阻塞特性。
  • 总耗时接近于最慢任务的耗时,而非任务耗时之和。
3.2 顺序保证
  • 结果列表的顺序与输入任务的顺序一致,与完成顺序无关。
3.3 异常处理
  • 默认(return_exceptions=False):任意任务失败,gather 抛出异常,未完成任务继续运行但结果不可用。
  • return_exceptions=True:所有任务完成,异常作为结果返回。
3.4 取消支持
  • 如果 gather 被取消(通过 Task.cancel()),所有子任务会被取消。

示例

import asyncio

async def task(name):
    await asyncio.sleep(10)
    return name

async def main():
    tasks = [task(name) for name in ["A", "B"]]
    try:
        await asyncio.wait_for(asyncio.gather(*tasks), timeout=1)
    except asyncio.TimeoutError:
        print("Timed out")

asyncio.run(main())

输出

Timed out

4. 应用场景

4.1 并发网络请求

并发发送多个 HTTP 请求(如爬虫、API 调用)。

示例(结合 aiohttp):

import asyncio
import aiohttp

async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

async def main():
    urls = ["https://example.com"] * 3
    results = await asyncio.gather(*(fetch(url) for url in urls))
    print(f"Fetched {len(results)} pages")

asyncio.run(main())
4.2 并发数据库操作

并发执行数据库查询。

示例(结合 aiosqlite):

import asyncio
import aiosqlite

async def query(db_name, id):
    async with aiosqlite.connect(db_name) as db:
        async with db.execute("SELECT ? AS id", (id,)) as cursor:
            return await cursor.fetchone()

async def main():
    tasks = [query(":memory:", i) for i in range(3)]
    results = await asyncio.gather(*tasks)
    print(results)  # 输出: [(0,), (1,), (2,)]

asyncio.run(main())
4.3 结合 asyncio.Semaphore

限制并发任务数量。

示例

import asyncio

async def task(name, semaphore):
    async with semaphore:
        await asyncio.sleep(1)
        return name

async def main():
    semaphore = asyncio.Semaphore(2)  # 限制 2 个并发
    tasks = [task(str(i), semaphore) for i in range(5)]
    results = await asyncio.gather(*tasks)
    print(results)

asyncio.run(main())
4.4 批量处理任务

并发处理多个独立任务(如文件处理)。

示例

import asyncio

async def process_file(filename):
    await asyncio.sleep(1)  # 模拟处理
    return f"Processed {filename}"

async def main():
    files = ["file1.txt", "file2.txt", "file3.txt"]
    results = await asyncio.gather(*(process_file(f) for f in files))
    print(results)

asyncio.run(main())

5. 最佳实践

  1. 使用 return_exceptions=True 处理异常

    • 避免单个任务失败导致整体失败。
    • 示例:
      results = await asyncio.gather(*tasks, return_exceptions=True)
      for result in results:
          if isinstance(result, Exception):
              print(f"Error: {result}")
          else:
              print(result)
      
  2. 结合 asyncio.create_task

    • 提前调度任务,优化并发启动。
    • 示例:
      tasks = [asyncio.create_task(coro) for coro in coroutines]
      await asyncio.gather(*tasks)
      
  3. 限制并发

    • 使用 asyncio.Semaphore 避免资源过载。
    • 示例:
      semaphore = asyncio.Semaphore(5)
      async def limited_task():
          async with semaphore:
              await do_work()
      
  4. 异常和日志

    • 捕获异常,记录日志,方便调试。
    • 示例:
      import logging
      logging.basicConfig(level=logging.INFO)
      
      async def main():
          try:
              await asyncio.gather(*tasks)
          except Exception as e:
              logging.error(f"Error: {e}")
      
  5. 测试异步代码

    • 使用 pytestpytest-asyncio 测试 gather 行为。
    • 示例:
      import pytest
      import asyncio
      
      @pytest.mark.asyncio
      async def test_gather():
          async def task():
              return 42
          results = await asyncio.gather(task(), task())
          assert results == [42, 42]
      

6. 注意事项

  1. 版本要求

    • asyncio.gather() 需要 Python 3.5+。
    • 示例:
      import sys
      assert sys.version_info >= (3, 5), "Requires Python 3.5+"
      
  2. 异常传播

    • 默认情况下,单个任务的异常会取消 gather,需设置 return_exceptions=True 或捕获异常。
    • 示例:
      try:
          await asyncio.gather(fail(), success())
      except ValueError:
          print("Caught error")
      
  3. 任务取消

    • 如果 gather 被取消,所有子任务也会被取消。
    • 示例:
      task = asyncio.create_task(asyncio.gather(*tasks))
      task.cancel()
      
  4. 性能优化

    • gather 适合 I/O 密集型任务,CPU 密集型任务应使用 multiprocessing
    • 示例:
      from multiprocessing import Pool
      
  5. 调试并发

    • 使用 asyncio.all_tasks() 或日志检查任务状态。
    • 示例:
      print(asyncio.all_tasks())
      

7. 总结

asyncio.gather() 是 Python 异步编程中用于并发运行多个协程并收集结果的强大工具。其核心特点包括:

  • 定义:并发执行可等待对象,返回结果列表。
  • 用法:通过 await asyncio.gather(*coroutines) 运行任务。
  • 应用:网络请求、数据库查询、批量任务处理。
  • 最佳实践:处理异常、限制并发、测试行为。

结合 asyncio.Semaphore 等工具,gather 可以高效管理异步并发。

参考文献

  • Python 官方文档:https://docs.python.org/3/library/asyncio-task.html#asyncio.gather
  • Real Python 异步教程:https://realpython.com/async-io-python/

你可能感兴趣的:(Python基础,asyncio,gather,python)