Функция asyncio.gather(*coros) одновременно запускает все awaitable-объекты, переданные как последовательность, и передаёт их для запуска в цикле событий. Функция asyncio.gather() является awaitable-объектом и запускается с оператором await. Если вы передаёте функции gather() сопрограмму, то она будет автоматически назначена как задача типа <class ‘_asyncio.Task’>.
asyncio.gather(*aws) возвращает список готовых результатов, которые были выполнены объектами ожидания. Это значит, что вы сначала настраиваете и создаёте задачи, после чего передаёте распакованный список в функцию gather(). Когда результат с задач получен, gather() вернёт список, состоящий из результатов выполнения каждой сопрограммы.
Код ниже демонстрирует, как передавать сразу несколько сопрограмм в функцию gather(). В этом коде на каждой итерации происходит одновременный запуск двух сопрограмм.
import asyncio import random async def two(x): await asyncio.sleep(random.randint(1, 3)) print(x) async def one(x): await asyncio.sleep(random.randint(1, 3)) print(x) async def main(): for x in range(5): await asyncio.gather(one(1), two(2)) asyncio.run(main())
Для создания асинхронных парсеров вы, скорее всего, будете использовать подход, который представлен ниже. Он позволяет генерировать запуск сопрограмм в любом количестве, которое будет зависеть только от того, сколько страниц вам нужно обработать. В этом коде мы создали список lst, который генерирует 10 чисел. Эти числа мы передаём в сопрограмму one(x), предварительно создав задачу для выполнения функцией asyncio.create_task(). Как вы помните, результатом выполнения этой функции является объект <class ‘_asyncio.Task’>. Каждая созданная задача отправляется в созданный заранее список lst_tasks, который содержит десять задач. Далее этот список в распакованном виде мы передаём в функцию gather() для одновременного запуска всех задач в асинхронном стиле.
Сопрограмма one() имитирует свою деятельность простым засыпанием. Каждое её засыпание можно сравнить с отправкой запроса к серверу, ведь по сути, циклу событий нет разницы, по какой причине сопрограмма передаёт управление следующему awaitable-объекту.
import asyncio import random async def one(x): await asyncio.sleep(random.randint(1, 3)) print(x) async def main(): lst = [x for x in range(10)] lst_tasks = [] for x in lst: task = asyncio.create_task(one(x)) lst_tasks.append(task) await asyncio.gather(*lst_tasks) asyncio.run(main()) #Результат 2 6 9 0 4 1 8 5 7 3
Функция asyncio.gather() в некоторых случаях полезна тем, что она помогает группировать сопрограммы и запускать их одновременно. Это может пригодиться для одновременного парсинга нескольких разных сайтов. Есть точка входа — сопрограмма main(), которая выполняет сбор всех сопрограмм и их запуск. И есть сопрограммы one(), two(), three(), которые могут собирать данные с выбранных сайтов. Также стоит помнить, что в сопрограмму asyncio.gather() необходимо передавать распакованное множество. Распаковка происходит с помощью символа *.
*Под множеством здесь подразумевается не конкретный тип данных set(), а понятие множества в целом: такие множества, как list(), tuple(), set() и др.
import asyncio import random async def one(x): await asyncio.sleep(random.randint(1, 3)) print(x) async def two(x): await asyncio.sleep(random.randint(1, 3)) print(x) async def three(x): await asyncio.sleep(random.randint(1, 3)) print(x) async def main(): group1 = asyncio.gather(*[one(i) for i in range(1, 10)]) group2 = asyncio.gather(*[two(i) for i in range(1, 10)]) group3 = asyncio.gather(*[three(i) for i in range(1, 10)]) await asyncio.gather(group1, group2, group3) asyncio.run(main()) #Результат 3 6 8 ... 4 2 5