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 em simultâneo e esperar por todos eles – é asyncio.gather().
8.4.1. A forma básica¶
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á pela ordem dos argumentos para o gather, não pela ordem em que as corrotinas terminaram – "c" retornou primeiro mas ainda é a terceira entrada. Segundo, a chamada demorou 200 ms no total, não 350 ms: os três sleeps correram em simultâneo, e gather() retorna assim que o mais lento terminar.
Ambos os factos têm a mesma origem. gather() envolve cada argumento que ainda não é uma tarefa (as corrotinas no exemplo), agenda-as no ciclo, suspende a corrotina chamante até que todas elas terminem, e depois devolve os seus resultados pela ordem original.
8.4.2. Quando recorrer a ele¶
Sempre que a aplicação tem N operações aguardáveis e quer os resultados de todas antes de continuar. Exemplos típicos:
Emitir vários pedidos de rede em paralelo e aguardar todas as respostas.
Ler de vários sensores em simultâneo antes de processar o resultado combinado.
Juntar várias tarefas auxiliares de curta duração no final de uma fase da aplicação.
Não é a ferramenta certa para tarefas de segundo plano de longa duração que a aplicação inicia e deixa correr durante todo o tempo de vida do programa – essas continuam a ser trabalho de create_task(). gather() é para o padrão de fan-out / fan-in: dividir o trabalho, fazê-lo em simultâneo, reunir.
8.4.3. Exceções no grupo¶
Se qualquer um dos awaitables recolhidos lançar uma exceção, o comportamento padrão é relançar a exceção para fora da chamada gather. Os irmãos que ainda não terminaram são cancelados em segundo plano.
Normalmente é isso que uma aplicação quer – um de N trabalhos paralelos falhou, portanto a operação combinada falhou, portanto pare de gastar tempo com o resto. Por vezes a aplicação quer o oposto: deixar cada awaitable terminar (ou falhar) de forma independente, e inspecionar os resultados depois. Para isso, passe return_exceptions=True
results = await asyncio.gather(
fetch_or_fail("ok"),
fetch_or_fail("bad"),
return_exceptions=True,
)
# results == ["ok-value", OSError(...)]
Cada entrada na lista devolvida é agora um valor de retorno normal ou a exceção que o awaitable correspondente lançou. O chamador verifica isinstance(r, Exception) para os distinguir.
8.4.4. Cancelamento¶
Cancelar o próprio gather – cancelando a tarefa que o estava a aguardar – cancela cada awaitable ainda em execução dentro dele. A página de timeouts e cancelamento cobre em detalhe como o cancelamento se propaga pela cadeia de chamadas.