Сообщить об ошибке.

Функция gather() модуля asyncio в Python

Планирование выполнения нескольких задач одновременно

Синтаксис:

import asyncio

await asyncio.gather(*aws, loop=None, return_exceptions=False)

Параметры:

  • *aws - последовательность объектов ожидания,
  • loop=None - параметр цикла (устарел и будет удален в Python 3.10),
  • return_exceptions=False - обработка исключений.

Возвращаемое значение:

Описание:

Функция gather() модуля asyncio одновременно запускает объекты awaitable, переданные в функцию как последовательность *aws.

Функция asyncio.gather() представляет то же объект ожидания awaitable и запускается оператором await.

Если какой-либо объект awaitable в последовательности *aws является сопрограммой, то она автоматически назначается как задача asyncio.Task.

Если все awaitable объекты завершены успешно, то результатом является совокупный список возвращенных значений этих объектов. Порядок значений результатов соответствует порядку переданных объектов в последовательность aws.

Если аргумент return_exceptions=False (по умолчанию), то первое появившееся исключение, немедленно распространяется на ту задачу, в которой оно возникло в момент ожидания asyncio.gather(). При этом другие объекты в последовательности aws не будут отменены и продолжат выполнение.

Если return_exceptions=True, то исключения обрабатываются так же, как успешные результаты и передаются в совокупный список результатов.

Если выполнение asyncio.gather() отменяется, то все отправленные в функцию объекты, которые еще не завершены, также отменяются.

Если какая-либо задача Task или объект Future отменяется в последовательности aws, то она обрабатывается так, как если бы она вызвала исключение asyncio.CancelledError - в этом случае вызов самой функции asyncio.gather() не отменяется. Это делается для того, чтобы отмена одной отправленной Task/Future не привела к отмене других Task/Future.

Примечание. Более современный способ одновременного создания и выполнения задач и ожидания их завершения - asyncio.TaskGroup.

Примечание: Если аргумент return_exceptions=False, то отмена функции asyncio.gather() после того, как она была отмечена как выполненная не отменит никаких отправленных в нее объектов awaitable. Например, функция asyncio.gather() может быть помечена как выполненная, после передачи исключения вызывающей стороне, поэтому вызов метода gather.cancel() после перехвата исключения, вызванного одним из объектов, который ждет в gather не отменяет другие awaitable объекты.

Изменено в Python 3.7: Если сама функция gather() отменена, то отмена распространяется независимо от значения аргумента return_exceptions.

Изменено в Python 3.10: Удален аргумент loop.

Устарело, начиная с Python 3.10: выдается предупреждение об устаревании, если не предоставлены позиционные аргументы или не все позиционные аргументы являются объектами, подобными Future, и нет запущенного цикла событий.

Обратите внимание, что запускаемые сопрограммы (асинхронные функции) не должны содержать внутри себя операции, блокирующие ход выполнения программы! Другими словами, нельзя какую либо встроенную синхронную функцию (типа socket.getnameinfo()) обернуть в асинхронную функцию (async def ...) и думать, что этот код будет работать асинхронно. НЕТ. Такая сопрограмма так же будет блокировать остальной код.

Для запуска из асинхронного кода функций, которые могут блокировать ход выполнения программы используйте субпроцессы или функцию, доступную с Python 3.9 asyncio.to_thread().

Пример вызова нескольких задач одновременно:

import asyncio

async def factorial(name, number):
    f = 1
    for i in range(2, number + 1):
        print(f"Task {name}: Compute factorial({i})...")
        await asyncio.sleep(1)
        f *= i
    print(f"Task {name}: factorial({number}) = {f}")

async def main():
    args = [('A', 2), ('B', 3), ('C', 4)]
    tasks=[]
    for arg in args:
        # создаем задачи
        task = factorial(*arg)
        # складываем задачи в список
        tasks.append(task)
        
    # планируем одновременные вызовы
    L = await asyncio.gather(*tasks)
    print(L)

if __name__ == '__main__':
    # Запускаем цикл событий
    results = asyncio.run(main())

# Expected output:
#
#     Task A: Compute factorial(2)...
#     Task B: Compute factorial(2)...
#     Task C: Compute factorial(2)...
#     Task A: factorial(2) = 2
#     Task B: Compute factorial(3)...
#     Task C: Compute factorial(3)...
#     Task B: factorial(3) = 6
#     Task C: Compute factorial(4)...
#     Task C: factorial(4) = 24

Пример объединения нескольких сопрограмм в группы вызовов.

В примере, группу спланированных задач можно отменить, вызвав например group2.cancel() или можно отменить сразу все задачи, вызвав all_result.cancel().

Смотрите также аргумент return_exceptions=True функции asyncio.gather().

import asyncio, random
from pprint import pprint

async def worker(tag):
    print('Run:', tag)
    await asyncio.sleep(random.uniform(1, 3))
    print('Done:', tag)
    return tag


async def main():
    # объединяем сопрограммы в группы, для планирования запуска
    group1 = asyncio.gather(*[worker(f'Группа 1.{i}') for i in range(1, 6)])
    group2 = asyncio.gather(*[worker(f'Группа 2.{i}') for i in range(1, 4)])
    group3 = asyncio.gather(*[worker(f'Группа 3.{i}') for i in range(1, 5)])
    # асинхронно запускаем созданные группы оператором `await` 
    all_result = await asyncio.gather(group1, group2, group3)
    # смотрим результаты
    pprint(all_result)

if __name__ == '__main__':
    results = asyncio.run(main())

# Run: Группа 1.1
# Run: Группа 1.2
# Run: Группа 1.3
# Run: Группа 1.4
# Run: Группа 1.5
# Run: Группа 2.1
# Run: Группа 2.2
# Run: Группа 2.3
# Run: Группа 3.1
# Run: Группа 3.2
# Run: Группа 3.3
# Run: Группа 3.4
# Done: Группа 1.3
# Done: Группа 1.2
# Done: Группа 1.5
# Done: Группа 3.1
# Done: Группа 1.1
# Done: Группа 2.2
# Done: Группа 1.4
# Done: Группа 3.3
# Done: Группа 2.1
# Done: Группа 2.3
# Done: Группа 3.4
# Done: Группа 3.2
# [['Группа 1.1', 'Группа 1.2', 'Группа 1.3', 'Группа 1.4', 'Группа 1.5'],
#  ['Группа 2.1', 'Группа 2.2', 'Группа 2.3'],
#  ['Группа 3.1', 'Группа 3.2', 'Группа 3.3', 'Группа 3.4']]