Что делает и для чего используется asyncio.gather в Python

Ответ

asyncio.gather() — это функция, которая позволяет запускать несколько асинхронных операций (корутин или Future) конкурентно и агрегировать их результаты.

Основная задача — выполнить группу независимых асинхронных задач параллельно, а не последовательно, и дождаться завершения всех из них. Это значительно сокращает общее время выполнения.

Ключевые особенности:

  • Конкурентный запуск: Все переданные корутины начинают выполняться в цикле событий практически одновременно.
  • Сбор результатов: Возвращает список результатов в том же порядке, в котором были переданы задачи.
  • Ожидание завершения: Блокирует выполнение до тех пор, пока все задачи не будут выполнены.

Пример:

import asyncio
import time

async def task(name, delay):
    print(f"Задача {name} началась, ждет {delay} сек.")
    await asyncio.sleep(delay)
    print(f"Задача {name} завершена.")
    return f"Результат от {name}"

async def main():
    start_time = time.time()

    # Задачи будут выполняться параллельно
    results = await asyncio.gather(
        task("A", 2),
        task("B", 1)
    )

    end_time = time.time()
    print(f"Результаты: {results}") # ['Результат от A', 'Результат от B']
    # Общее время ~2 секунды, а не 3 (2+1)
    print(f"Общее время выполнения: {end_time - start_time:.2f} сек.")

asyncio.run(main())

Обработка исключений Если в одной из задач возникает исключение, gather по умолчанию немедленно пробрасывает его, отменяя остальные задачи. Чтобы собрать все результаты, включая исключения, используется флаг return_exceptions=True.

Ответ 18+ 🔞

Смотри, вот тебе про asyncio.gather() без соплей и заумных терминов, на пальцах.

Представь, что у тебя есть куча дел, которые можно делать одновременно, пока ты ждёшь ответа от сервера, читаешь файлы или просто тупишь в ожидании. Так вот, gather — это твой личный надсмотрщик, который кричит этим задачам: «А ну, все нахуй, одновременно начинаем!» и потом терпеливо ждёт, пока каждая не отчитается.

Что он делает, по сути:

  1. Запускает всё сразу. Не как дурак — сделал одно, потом другое, потом третье. Нет, блядь, все задачи стартуют в одном событийном цикле и работают конкурентно.
  2. Ждёт всех до последнего. Пока последний ленивый уёбок не закончит своё дело, gather не отпустит тебя дальше по коду.
  3. Аккуратно собирает результаты. И выдаёт тебе их в том же порядке, в котором ты задачи передал. Удобно, ёпта.

Вот смотри, как это выглядит в жизни:

import asyncio
import time

async def task(name, delay):
    print(f"Задача {name} началась, ждет {delay} сек.")
    await asyncio.sleep(delay)
    print(f"Задача {name} завершена.")
    return f"Результат от {name}"

async def main():
    start_time = time.time()

    # Сейчас магия начнётся. Обе задачи стартанут ОДНОВРЕМЕННО.
    results = await asyncio.gather(
        task("A", 2),  # Эта будет спать 2 секунды
        task("B", 1)   # А эта всего 1
    )

    end_time = time.time()
    print(f"Результаты: {results}") # ['Результат от A', 'Результат от B']
    # И вот охуенный момент: общее время будет ~2 секунды, а не 3 (2+1)!
    print(f"Общее время выполнения: {end_time - start_time:.2f} сек.")

asyncio.run(main())

А что с ошибками, спросишь? А вот тут интересно. По умолчанию, если одна задача пойдёт по пизде и выбросит исключение, gather сразу же заорёт «ВСЁ, ПИЗДЕЦ!» и отменит все остальные. Но если ты не идиот и хочешь получить ВСЕ результаты — и успешные, и проваленные — то передай флаг return_exceptions=True. Тогда он соберёт всё в кучу: и нормальные ответы, и объекты исключений. Дальше уже сам разбирайся, где что. Хитрая жопа, но иногда очень нужная.