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.