8.4. Gather

O capítulo anterior mostrou como asyncio.create_task() agenda uma corrotina e não espera por ela. A operação complementar – executar vários awaitables simultaneamente e esperar por todos eles – é asyncio.gather().

8.4.1. O formato básico

import asyncio

async def fetch(name, delay_ms):
    await asyncio.sleep_ms(delay_ms)
    return name

async def main():
    results = await asyncio.gather(
        fetch("a", 100),
        fetch("b", 200),
        fetch("c", 50),
    )
    print(results)

asyncio.run(main())

Saída:

['a', 'b', 'c']

Duas coisas a notar. Primeiro, a lista de resultados está na ordem dos argumentos passados ao gather, não na ordem em que as corrotinas terminaram – "c" retornou primeiro, mas ainda é a terceira entrada. Segundo, a chamada levou 200 ms no total, não 350 ms: os três sleeps foram executados simultaneamente, e gather() retorna assim que o mais lento é concluído.

Ambos os fatos vêm da mesma fonte. gather() envolve cada argumento que ainda não é uma tarefa (as corrotinas do exemplo), agenda-os no laço, suspende a corrotina chamadora até que todos terminem e, em seguida, retorna seus resultados na ordem original.

8.4.2. Quando recorrer a ele

Em qualquer lugar em que a aplicação tenha N operações awaitable e queira os resultados de todas elas antes de continuar. Exemplos típicos:

  • Emitir várias requisições de rede em paralelo e esperar por todas as respostas.

  • Ler de vários sensores simultaneamente antes de processar o resultado combinado.

  • Juntar várias tarefas auxiliares de curta duração ao final de uma fase da aplicação.

Ele não é a ferramenta certa para tarefas de segundo plano de longa duração que a aplicação inicia e deixa em execução durante toda a vida do programa – essas ainda são trabalho de create_task(). gather() é para o padrão fan-out / fan-in: dividir o trabalho, executá-lo simultaneamente e reunir.

8.4.3. Exceções no grupo

Se algum dos awaitables reunidos lançar uma exceção, o comportamento padrão é relançar a exceção para fora da chamada do gather. Os irmãos que ainda não terminaram são cancelados em segundo plano.

Isso geralmente é o que uma aplicação quer – um dos N trabalhos paralelos falhou, então a operação combinada falhou, então pare de gastar tempo com o restante. Às vezes a aplicação quer o oposto: deixar cada awaitable terminar (ou falhar) de forma independente e inspecionar os resultados depois. Passe return_exceptions=True para isso:

results = await asyncio.gather(
    fetch_or_fail("ok"),
    fetch_or_fail("bad"),
    return_exceptions=True,
)
# results == ["ok-value", OSError(...)]

Cada entrada na lista retornada agora é ou um valor de retorno normal ou a exceção que o awaitable correspondente lançou. O chamador verifica isinstance(r, Exception) para distingui-las.

8.4.4. Cancelamento

Cancelar o próprio gather – cancelando a tarefa que estava aguardando por ele – cancela todos os awaitables ainda em execução dentro dele. A página timeouts e cancelamento aborda em detalhes como o cancelamento se propaga pela cadeia de chamadas.