Trilha de aprendizado · Nível 13 · Tutorial 8

Coordenar tarefas com asyncio.TaskGroup

Ao concluir, você será capaz de manter tarefas relacionadas em um escopo comum que aguarda sua finalização, coordena cancelamentos diante de falhas e apresenta os erros sem abandonar tarefas.

  • Nível: Avançado
  • Duração: 25 min
  • 8 passos
Coordenar tarefas com asyncio.TaskGroup

O que você vai percorrer

  1. Vincular tarefas a um escopo responsável Entenda como o TaskGroup vincula tarefas relacionadas a um escopo que responde pelo encerramento delas. 2 min
  2. Abrir o TaskGroup e registrar tarefas Abra um escopo assíncrono, registre operações relacionadas nele e mantenha as referências às tarefas criadas. 3 min
  3. Recuperar resultados após o encerramento Use as referências às tarefas para consolidar resultados somente depois que o TaskGroup concluir sua saída assíncrona. 3 min
  4. Entender o encerramento provocado por falha Veja como o TaskGroup coordena o cancelamento e a limpeza das tarefas irmãs quando uma delas falha. 3 min
  5. Distinguir cancelamento isolado de falha Compare cancelamento, retorno e falha para consultar resultados de tarefas com segurança. 2 min
  6. Interpretar as falhas agrupadas Leia os erros apresentados por um TaskGroup como um conjunto de falhas efetivamente observadas durante seu encerramento. 3 min
  7. Tratar somente os subgrupos esperados Selecione falhas esperadas dentro de um ExceptionGroup sem esconder as demais. 4 min
  8. Aplicar e revisar a coordenação completa Pratique os quatro cenários centrais do TaskGroup em um script local e revise as garantias de coordenação, cancelamento e propagação de falhas. 5 min

O que você vai aprender

  • Criar tarefas relacionadas dentro de um TaskGroup.
  • Recuperar resultados após a saída bem-sucedida do grupo.
  • Explicar o cancelamento das tarefas restantes quando uma tarefa falha.
  • Tratar subgrupos de exceções esperadas com except* e preservar falhas não tratadas.

Antes de começar

  • Gerenciar tarefas e cancelamento no asyncio
  • Criar gerenciadores de contexto com classes

Passo 1 de 8

Vincular tarefas a um escopo responsável

Entenda como o TaskGroup vincula tarefas relacionadas a um escopo que responde pelo encerramento delas.

Um escopo responsável pelas tarefas

Concorrência estruturada

Ao criar tarefas relacionadas, não basta iniciá-las: alguém precisa acompanhar o encerramento delas. Na concorrência estruturada, esse responsável é um escopo.

Um TaskGroup reúne tarefas que pertencem à mesma operação. Enquanto esse escopo não termina, o grupo acompanha as tarefas que registrou. Assim, a responsabilidade de aguardar a finalização e coordenar o encerramento deixa de ficar espalhada pelo programa.

Tarefas contidas no grupo

A saída do escopo depende do encerramento das tarefas registradas.

Diagrama de um escopo TaskGroup contendo três tarefas relacionadas; uma seta aponta para a saída do escopo, bloqueada até que as três tarefas estejam encerradas.

O grupo delimita quais tarefas fazem parte da mesma operação e só conclui sua saída após cuidar delas.

O limite da responsabilidade

Estar perto não é pertencer

A responsabilidade do grupo alcança somente as tarefas que ele registra. Uma tarefa criada de outra forma pode estar no mesmo trecho de código e ainda assim ficar fora desse vínculo.

Em particular, chamar asyncio.create_task(...) dentro da região em que existe um grupo não inclui automaticamente essa tarefa no grupo. Ela continua exigindo acompanhamento explícito de quem a criou.

Exemplo

Compare a origem das tarefas

Imagine uma operação que inicia três tarefas:

  • Tarefa A: registrada pelo TaskGroup → o grupo responde pelo encerramento dela.
  • Tarefa B: registrada pelo TaskGroup → o grupo responde pelo encerramento dela.
  • Tarefa C: criada com asyncio.create_task(...) no mesmo trecho → ela não passa a ser responsabilidade do grupo apenas por proximidade.

O próximo passo mostrará a sintaxe para registrar A e B corretamente. Por enquanto, guarde a regra: o responsável é definido pela forma de criação, não pela posição visual no código.

Quem responde por cada tarefa?

Associe a situação à responsabilidade

Faça a correspondência correta.

Toque em um item e depois no par correspondente.

Passo 2 de 8

Abrir o TaskGroup e registrar tarefas

Abra um escopo assíncrono, registre operações relacionadas nele e mantenha as referências às tarefas criadas.

O contexto que pode aguardar

Use Python 3.12 ou superior

Nesta trilha, a referência é o Python 3.12 ou superior. asyncio.TaskGroup e a sintaxe except* existem desde o Python 3.11.

Um async with é adequado quando a entrada ou a saída de um contexto pode precisar de await. Por isso, ele usa os métodos aguardáveis __aenter__ e __aexit__. No caso de TaskGroup, a saída do contexto participa do encerramento coordenado das tarefas registradas.

Estrutura do contexto assíncrono

O corpo registra as operações no grupo; a saída assíncrona só acontece depois do trabalho coordenado pelo grupo.

Diagrama mostrando uma função assíncrona contendo um bloco async with TaskGroup. Dentro do bloco, três corrotinas são registradas como tarefas; uma seta na saída do bloco indica espera coordenada.

async with delimita o escopo responsável pelas tarefas registradas.

Registrar tarefas no grupo

`create_task` pertence ao grupo

Dentro de uma função definida com async def, abra o escopo com async with asyncio.TaskGroup() as grupo.

Passe a corrotina para grupo.create_task(...) e guarde a Task retornada. Registre todas as operações relacionadas antes de sair do bloco. Não faça await em cada criação: isso faria uma operação terminar antes de iniciar a próxima.

Atenção: asyncio.create_task(...) cria uma tarefa no laço de eventos, mas não a registra automaticamente neste TaskGroup.

Forma essencial

As variáveis tarefa_a e tarefa_b guardam as referências retornadas pelo grupo.

python
async with asyncio.TaskGroup() as grupo:
    tarefa_a = grupo.create_task(operacao_a())
    tarefa_b = grupo.create_task(operacao_b())

# O próximo step mostrará quando consultar essas referências.

Prática local: iniciar operações relacionadas

Execute este script

Crie um arquivo, por exemplo grupo.py, cole o código e execute python grupo.py. As pausas com asyncio.sleep apenas simulam operações de entrada e saída; não representam uma medição de tempo.

Observe que as três mensagens de início aparecem antes das mensagens de término, pois as tarefas foram registradas sem esperas individuais.

Script autocontido

Todas as operações usam somente a biblioteca padrão.

python
import asyncio


async def consultar(nome: str, demora: float) -> str:
    print(f"Início: {nome}")
    await asyncio.sleep(demora)
    print(f"Fim: {nome}")
    return f"dados de {nome}"


async def main() -> None:
    async with asyncio.TaskGroup() as grupo:
        usuarios = grupo.create_task(consultar("usuários", 0.2))
        pedidos = grupo.create_task(consultar("pedidos", 0.1))
        estoque = grupo.create_task(consultar("estoque", 0.15))

    print("O contexto do grupo foi encerrado.")
    # As referências usuarios, pedidos e estoque continuam disponíveis aqui.
    # Seus resultados serão tratados no próximo step.


if __name__ == "__main__":
    asyncio.run(main())

Dica

Guarde as referências

Mesmo quando ainda não precisa usar as tarefas logo após o bloco, guardar as referências torna explícita a relação entre cada operação iniciada e sua Task.

Verifique a estrutura

Complete a chamada

Para registrar baixar_catalogo() no TaskGroup chamado grupo, complete:

catalogo = grupo._____(baixar_catalogo())

Qual chamada registra a tarefa?

Dentro de async with asyncio.TaskGroup() as grupo:, qual linha registra sincronizar() no grupo?

Passo 3 de 8

Recuperar resultados após o encerramento

Use as referências às tarefas para consolidar resultados somente depois que o TaskGroup concluir sua saída assíncrona.

A saída do contexto é o ponto de consolidação

O corpo terminou; o grupo ainda pode estar trabalhando

Depois de registrar as tarefas, o corpo do async with pode chegar ao fim antes de elas terminarem. A saída assíncrona do TaskGroup então aguarda as tarefas registradas. Somente na linha após o bloco é seguro consolidar os resultados neste cenário: todas as tarefas retornam normalmente.

Do registro à consolidação

A linha de saída do async with é uma fronteira: o código posterior só é executado após o encerramento bem-sucedido das tarefas do grupo.

Diagrama mostrando três tarefas criadas dentro de um bloco TaskGroup, o fim do corpo do bloco, uma etapa de espera na saída assíncrona e três resultados disponíveis após o bloco.

O fim visual do corpo não é o fim do grupo: a saída do contexto aguarda as tarefas.

Dica

Não consulte cedo demais

Task.result() recupera um resultado já concluído; ela não espera a tarefa terminar. Se for chamada enquanto a tarefa ainda está pendente, pode levantar InvalidStateError. Deixe a consolidação para depois do async with.

Conserve as referências e organize a apresentação

O grupo não retorna uma lista de valores

TaskGroup coordena o ciclo de vida das tarefas, mas não devolve automaticamente uma lista de resultados. Guarde cada Task retornada por grupo.create_task() e associe-a à entrada correspondente. Assim, você pode apresentar os valores na ordem das entradas, mesmo que as tarefas terminem em outra ordem.

Execute este script localmente

Salve como resultados_taskgroup.py e execute com Python 3.11 ou superior: python resultados_taskgroup.py.

python
import asyncio


async def consultar(produto: str, demora: float) -> str:
    await asyncio.sleep(demora)
    return f"{produto}: disponível"


async def main() -> None:
    pedidos = [
        ("caderno", 0.20),
        ("caneta", 0.05),
        ("mochila", 0.10),
    ]
    tarefas: dict[str, asyncio.Task[str]] = {}

    async with asyncio.TaskGroup() as grupo:
        for produto, demora in pedidos:
            tarefas[produto] = grupo.create_task(consultar(produto, demora))
        print("Tarefas registradas; saindo do corpo do bloco.")

    print("O TaskGroup encerrou com sucesso.")
    resultados = [tarefas[produto].result() for produto, _ in pedidos]

    print("Resultados na ordem dos pedidos:")
    for resultado in resultados:
        print("-", resultado)


if __name__ == "__main__":
    asyncio.run(main())

Verifique o que a execução demonstrou

Relate sua observação

Após executar o script, indique: em que ponto os resultados são consultados, por que eles já estão disponíveis nesse ponto e por que a ordem impressa não prova uma ordem obrigatória de conclusão das tarefas.

Escreva pelo menos 80 caracteres (0/80).

Passo 4 de 8

Entender o encerramento provocado por falha

Veja como o TaskGroup coordena o cancelamento e a limpeza das tarefas irmãs quando uma delas falha.

Falha aciona o encerramento coordenado

Uma falha não deixa as irmãs em segundo plano

Se uma tarefa registrada no TaskGroup levanta uma exceção diferente de asyncio.CancelledError, o grupo inicia o encerramento: solicita o cancelamento das tarefas irmãs que ainda não terminaram.

Isso não é uma interrupção instantânea. O cancelamento é cooperativo: cada tarefa recebe CancelledError quando volta a um ponto em que pode responder a ele, normalmente em um await. Antes de apresentar a falha ao código externo, o grupo espera essas tarefas encerrarem.

Cadeia causal do encerramento

A ordem dos marcos é coordenada; a ordem em que tarefas irmãs imprimem mensagens não é garantida.

Diagrama mostrando uma tarefa que falha, sinais de cancelamento enviados a duas tarefas irmãs, etapas de limpeza e o TaskGroup apresentando o erro somente após todas encerrarem.

Falha comum → pedido de cancelamento às irmãs pendentes → limpeza e término → apresentação do erro.

Limpeza deve deixar o cancelamento seguir

Uma tarefa que limpa e relança o cancelamento

Execute este script no seu computador. As mensagens de limpeza das tarefas A e B podem aparecer em ordens diferentes.

python
import asyncio


async def acompanhar(nome: str) -> None:
    try:
        print(f"{nome}: iniciada")
        await asyncio.sleep(10)
        print(f"{nome}: concluída")
    except asyncio.CancelledError:
        print(f"{nome}: recebendo cancelamento")
        raise
    finally:
        print(f"{nome}: limpeza concluída")


async def falhar() -> None:
    await asyncio.sleep(0.1)
    raise RuntimeError("fonte indisponível")


async def principal() -> None:
    async with asyncio.TaskGroup() as grupo:
        grupo.create_task(acompanhar("A"))
        grupo.create_task(acompanhar("B"))
        grupo.create_task(falhar())
        await asyncio.sleep(5)
        print("Esta linha não é uma garantia")


asyncio.run(principal())

Atenção

Não suprima CancelledError por acidente

O finally executa a limpeza tanto no retorno normal quanto no cancelamento. Se você capturar asyncio.CancelledError para registrar algo, relance-o com raise. Engolir esse sinal pode atrasar ou comprometer a coordenação do encerramento.

O que observar

falhar() produz a falha comum. O grupo então pede cancelamento a A e B, aguarda a execução do finally de ambas e só depois deixa RuntimeError sair de asyncio.run.

O await asyncio.sleep(5) no corpo do async with pode ser interrompido pela falha; portanto, a última linha do bloco não é garantida. Ainda assim, o grupo coordena o encerramento das tarefas que registrou.

Reconstrua a sequência causal

Ordene os marcos

Considere que uma tarefa do grupo levanta RuntimeError enquanto duas irmãs ainda aguardam. Coloque os eventos na sequência causal correta.

  1. O TaskGroup apresenta a falha ao sair do contexto.
  2. Uma tarefa levanta RuntimeError.
  3. O TaskGroup solicita o cancelamento das tarefas irmãs pendentes.
  4. As irmãs respondem ao cancelamento, executam sua limpeza e terminam.

Explique a espera antes do erro

Justificativa curta

Por que o TaskGroup espera as tarefas irmãs terminarem antes de apresentar a falha que iniciou o encerramento? Inclua o papel da limpeza e explique por que não se deve depender da ordem das mensagens das irmãs.

Escreva pelo menos 80 caracteres (0/80).

Passo 5 de 8

Distinguir cancelamento isolado de falha

Compare cancelamento, retorno e falha para consultar resultados de tarefas com segurança.

Cancelamento não é falha comum

Uma filha cancelada não cancela automaticamente as irmãs

Em um TaskGroup, uma tarefa filha que termina com asyncio.CancelledError pode estar cancelada enquanto as demais continuam e retornam normalmente. Esse cancelamento isolado, por si só, não aciona a política aplicada quando uma tarefa falha com uma exceção comum, como ValueError.

Assim, o async with pode terminar sem levantar erro, mas isso não significa que toda Task tenha um valor de retorno.

Três desfechos possíveis

Compare o efeito de cada término sobre as tarefas irmãs e sobre a saída do grupo.

Diagrama comparando três tarefas em um TaskGroup: uma retorna normalmente, uma falha e cancela tarefas irmãs, e uma é cancelada isoladamente enquanto as irmãs terminam normalmente.

Retorno e cancelamento isolado podem permitir a saída normal; uma exceção comum em uma filha inicia o encerramento coordenado das irmãs.

Verifique antes de pedir o resultado

Cancelar uma filha e preservar as outras

Execute este script localmente com Python 3.11 ou superior. A tarefa lenta é cancelada, mas rapida ainda conclui.

python
import asyncio


async def trabalho(nome: str, demora: float) -> str:
    try:
        await asyncio.sleep(demora)
        return f"{nome}: concluído"
    finally:
        print(f"limpeza de {nome}")


async def main() -> None:
    async with asyncio.TaskGroup() as grupo:
        rapida = grupo.create_task(trabalho("rápida", 0.01))
        lenta = grupo.create_task(trabalho("lenta", 1.0))

        await asyncio.sleep(0)
        lenta.cancel()

    # O grupo já aguardou o encerramento de ambas as tarefas.
    for tarefa in (rapida, lenta):
        if tarefa.cancelled():
            print("tarefa cancelada: sem resultado")
        else:
            print(tarefa.result())


asyncio.run(main())

Dica

Ordem segura de consulta

Depois da saída do grupo, quando cancelamentos forem possíveis, consulte tarefa.cancelled() antes de chamar tarefa.result(). Para uma tarefa cancelada, result() levanta asyncio.CancelledError; cancelamento não é um resultado de sucesso.

Associe desfecho e consequência

Desfecho de uma tarefa filha

Relacione cada desfecho ao comportamento correspondente no TaskGroup.

Toque em um item e depois no par correspondente.

Saída do contexto e resultados

Se um TaskGroup termina sem levantar erro, então toda tarefa criada nele possui um valor que pode ser obtido com result().

Passo 6 de 8

Interpretar as falhas agrupadas

Leia os erros apresentados por um TaskGroup como um conjunto de falhas efetivamente observadas durante seu encerramento.

Uma saída pode reunir falhas

Erros observados em conjunto

Quando uma tarefa do TaskGroup falha com uma exceção comum, o grupo inicia o encerramento coordenado. Ao terminar esse processo, ele apresenta as falhas comuns que foram realmente observadas em um ExceptionGroup.

O nome é literal: trata-se de um grupo estruturado de exceções. Mesmo que só uma falha comum tenha sido observada, o TaskGroup a apresenta agrupada. Portanto, não suponha que receberá diretamente um único ValueError, por exemplo.

Árvore de falhas

Leia o grupo como uma árvore: o nó externo representa a apresentação feita pelo TaskGroup; as folhas representam falhas comuns observadas.

Diagrama de uma árvore com ExceptionGroup no topo, uma folha ValueError e uma folha RuntimeError; uma tarefa cancelada aparece separada, fora da árvore.

CancelledError de uma tarefa cancelada não entra como folha de falha comum no ExceptionGroup.

Exemplo

Uma falha ainda pode surgir na limpeza

Imagine este registro possível:

processar: iniciou
salvar: iniciou
processar: ValueError
salvar: recebeu cancelamento; iniciou limpeza
salvar: RuntimeError durante a limpeza

A falha de processar faz o grupo solicitar o cancelamento de salvar. Mas salvar ainda executa sua limpeza e pode falhar nela. Ao fim, o grupo pode apresentar um ExceptionGroup com as duas folhas: ValueError e RuntimeError.

As mensagens e a ordem podem variar. O ponto importante é que ambas ocorreram antes de o encerramento terminar.

O que a árvore não afirma

Observado não é hipotético

O grupo inclui falhas que aconteceram de fato. Ele não adivinha erros que uma tarefa cancelada poderia produzir se tivesse continuado a execução.

Assim, se uma tarefa recebeu cancelamento antes de alcançar um futuro raise KeyError, esse KeyError hipotético não aparece. E, se ela termina apenas com CancelledError, isso não é incluído como falha comum na árvore.

Dica

Evite previsões rígidas

Não programe assumindo uma quantidade fixa de folhas nem a ordem entre erros de tarefas irmãs. A intercalação entre tarefas e o momento em que respondem ao cancelamento podem mudar o conjunto observado.

Escolha a árvore compatível

Considere este registro:

buscar: iniciou
registrar: iniciou
buscar: ValueError
registrar: recebeu cancelamento
registrar: RuntimeError na limpeza

Qual representação é compatível com as falhas apresentadas pelo TaskGroup?

Ler sem inventar falhas

Explique a leitura

Uma tarefa validar produz ValueError. Isso faz o grupo cancelar exportar. Durante a limpeza, exportar produz OSError; ela também teria produzido KeyError mais tarde, mas não chegou a esse ponto.

Quais falhas podem aparecer no ExceptionGroup e por que CancelledError e KeyError não devem ser acrescentados?

Escreva pelo menos 80 caracteres (0/80).

Resumo

Leitura segura de ExceptionGroup

  • O TaskGroup apresenta falhas comuns observadas em um ExceptionGroup, inclusive quando há apenas uma folha.
  • Uma tarefa cancelada pode ainda executar limpeza e produzir outra falha que será observada.
  • CancelledError não é uma falha comum adicionada à árvore.
  • Não inclua erros hipotéticos de tarefas que foram interrompidas antes de produzi-los.
  • A quantidade, a ordem e a estrutura de subgrupos não devem ser presumidas.

Passo 7 de 8

Tratar somente os subgrupos esperados

Selecione falhas esperadas dentro de um ExceptionGroup sem esconder as demais.

O tratamento recebe os erros na saída do grupo

Posicione o try por fora

Um TaskGroup apresenta suas falhas ao sair do async with. Por isso, envolva o bloco inteiro com try: os tratadores recebem o ExceptionGroup produzido no encerramento.

Use except* Tipo para selecionar as exceções desse tipo nas folhas do grupo. A variável após as é um subgrupo, mesmo que contenha apenas uma exceção correspondente.

Seleção sem apagar o restante

Cada cláusula except* extrai somente a parte compatível. O que não for selecionado continua se propagando.

Diagrama de um TaskGroup que termina e produz uma árvore de exceções. O ramo ValueError segue para um tratador except* ValueError, enquanto o ramo KeyError permanece em propagação.

except* trata um subgrupo por tipo; os ramos não tratados não desaparecem.

Trate o tipo previsto e preserve o inesperado

Exemplo seletivo

Execute com Python 3.11 ou superior. A falha de validação é registrada; a falha de chave continua sendo apresentada depois.

python
import asyncio


async def validar() -> None:
    await asyncio.sleep(0)
    raise ValueError("formato de pedido inválido")


async def buscar_configuracao() -> None:
    await asyncio.sleep(0)
    raise KeyError("REGIAO")


async def executar() -> None:
    try:
        async with asyncio.TaskGroup() as grupo:
            grupo.create_task(validar())
            grupo.create_task(buscar_configuracao())
    except* ValueError as erros_de_validacao:
        print("Falha esperada:", erros_de_validacao)


asyncio.run(executar())

Atenção

Não capture tudo por acidente

Não substitua o tratamento seletivo por except* Exception apenas para evitar a propagação: isso também esconderia defeitos que você não decidiu tratar. No exemplo, o subgrupo com KeyError permanece não tratado e é propagado.

No mesmo try, Python não permite misturar except e except*. Se você usa except*, todos os tratadores daquele try devem usar except*.

Complete a seleção correta

Um tipo dentro do grupo

Complete o tratador:

try:
    async with asyncio.TaskGroup() as grupo:
        grupo.create_task(validar())
except___ ValueError as erros:
    registrar(erros)

Efeito sobre tarefas e resultados

Tratamento não desfaz o encerramento

Quando uma tarefa falha, o grupo já solicitou o cancelamento das irmãs que ainda estavam ativas e aguardou o encerramento delas. Tratar um subgrupo com except* acontece depois disso: não reinicia tarefas canceladas nem lhes atribui um resultado.

Só consolide task.result() no caminho em que o async with terminou com sucesso. Se houve falhas tratadas ou cancelamentos possíveis, defina explicitamente outra política para esses estados.

Preveja os subgrupos

Um TaskGroup observa uma ValueError e uma KeyError. O código tem somente:

except* ValueError as erros:
    registrar(erros)

Explique o que essa cláusula recebe, o que acontece com a KeyError e por que ela não retoma as tarefas que foram canceladas.

Escreva pelo menos 80 caracteres (0/80).

Passo 8 de 8

Aplicar e revisar a coordenação completa

Pratique os quatro cenários centrais do TaskGroup em um script local e revise as garantias de coordenação, cancelamento e propagação de falhas.

Quatro desfechos, um escopo responsável

Use o mesmo critério em cada cenário

Em todos os cenários, as tarefas são registradas no mesmo TaskGroup. O que muda é o desfecho: retorno normal, cancelamento isolado, falha esperada ou uma falha inesperada adicional. A saída do async with continua sendo o ponto que coordena o encerramento das tarefas registradas.

Comparação dos cenários

Observe quais tarefas podem fornecer resultado e quando a falha precisa continuar se propagando.

Diagrama com quatro colunas: sucesso com três tarefas concluídas; cancelamento isolado com uma tarefa cancelada e duas concluídas; falha esperada cancelando tarefas irmãs; falha esperada junto de uma falha inesperada durante a limpeza.

Resultado só é consultado no caminho sem falhas do grupo; uma tarefa cancelada também não possui resultado.

Dica

Não use a ordem como evidência

As mensagens de tarefas irmãs podem aparecer em ordens diferentes. Verifique propriedades: todas as tarefas registradas encerraram; a limpeza ocorreu; resultados foram lidos apenas quando válidos; e a parte inesperada não foi ocultada.

Prática local: execute e alterne o cenário

Prepare o experimento

Crie um arquivo chamado taskgroup_revisao.py, copie o script e execute python taskgroup_revisao.py. Altere apenas a constante CENARIO para testar "sucesso", "cancelamento", "esperado" e "misto". Use Python 3.11 ou superior; a referência da trilha é Python 3.12+.

taskgroup_revisao.py

Script autocontido: cada cenário mostra um desfecho coordenado pelo grupo.

python
import asyncio

CENARIO = "sucesso"  # sucesso, cancelamento, esperado ou misto


async def operacao(nome: str, acao: str, eventos: list[str]) -> str:
    eventos.append(f"início:{nome}")
    try:
        if acao == "falhar":
            await asyncio.sleep(0.01)
            raise ValueError(f"dado inválido em {nome}")

        if acao == "cancelar":
            await asyncio.sleep(0.01)
            raise asyncio.CancelledError()

        if acao == "falhar_na_limpeza":
            # Esta tarefa será cancelada pela falha da irmã.
            await asyncio.sleep(1)

        await asyncio.sleep(0.03)
        eventos.append(f"retorno:{nome}")
        return nome.upper()
    finally:
        eventos.append(f"limpeza:{nome}")
        if acao == "falhar_na_limpeza":
            raise KeyError(f"falha inesperada na limpeza de {nome}")


def acoes_do(cenario: str) -> dict[str, str]:
    if cenario == "sucesso":
        return {"a": "ok", "b": "ok", "c": "ok"}
    if cenario == "cancelamento":
        return {"a": "ok", "b": "cancelar", "c": "ok"}
    if cenario == "esperado":
        return {"a": "ok", "b": "falhar", "c": "ok"}
    if cenario == "misto":
        return {"a": "falhar", "b": "falhar_na_limpeza", "c": "ok"}
    raise ValueError("CENARIO inválido")


async def executar(cenario: str) -> None:
    eventos: list[str] = []
    tarefas: dict[str, asyncio.Task[str]] = {}

    try:
        async with asyncio.TaskGroup() as grupo:
            for nome, acao in acoes_do(cenario).items():
                tarefas[nome] = grupo.create_task(operacao(nome, acao, eventos))
    except* ValueError as erros_esperados:
        print("ValueError tratado:", erros_esperados)
        print("Eventos após encerramento:", eventos)
    else:
        resultados = {
            nome: tarefa.result()
            for nome, tarefa in tarefas.items()
            if not tarefa.cancelled()
        }
        canceladas = [nome for nome, tarefa in tarefas.items() if tarefa.cancelled()]
        print("Resultados válidos:", resultados)
        print("Tarefas canceladas:", canceladas)
        print("Eventos após encerramento:", eventos)


if __name__ == "__main__":
    asyncio.run(executar(CENARIO))

Leia os resultados pelos invariantes

O que observar em cada execução

  • sucesso: três resultados e três eventos de limpeza.
  • cancelamento: b aparece entre as canceladas; a e c ainda retornam normalmente.
  • esperado: o ValueError é tratado; as tarefas que ainda estavam pendentes recebem cancelamento, e os eventos de limpeza aparecem.
  • misto: o ValueError é tratado, mas o subgrupo com KeyError permanece sem tratamento e é propagado. Isso é intencional.

Atenção

Não transforme falha em sucesso

No caminho que entrou em except*, não consolide resultados como se o grupo tivesse terminado com sucesso. E, no caminho normal, teste task.cancelled() antes de chamar task.result(): uma tarefa cancelada não oferece valor de retorno.

Registro da sua execução

Depois de executar ao menos dois cenários, registre: (1) um caso em que resultados podem ser consolidados, (2) o que acontece com as irmãs no cancelamento isolado ou na falha e (3) como você confirmou que uma falha inesperada não foi ocultada.

Escreva pelo menos 180 caracteres (0/180).

Síntese da coordenação estruturada

Resumo

Decisões essenciais com TaskGroup

Use o grupo como o escopo responsável pelas tarefas relacionadas.

  • Registre as corrotinas com grupo.create_task e mantenha as referências retornadas.
  • A saída do async with só termina depois de coordenar o encerramento das tarefas registradas.
  • Recupere Task.result() apenas após uma saída sem falhas e somente para tarefas não canceladas.
  • Uma exceção comum cancela as irmãs ainda pendentes, mas o cancelamento é cooperativo e a limpeza ainda precisa terminar.
  • Use except* fora do async with para tratar somente os tipos previstos; subgrupos não tratados continuam se propagando.
  • TaskGroup coordena ciclos de vida; ele não converte cancelamento em sucesso nem garante interrupção imediata.

Tutorial concluído

Parabéns! Você concluiu: Coordenar tarefas com asyncio.TaskGroup

Muito bem! Você consegue organizar tarefas em um TaskGroup, consolidar apenas resultados válidos e tratar falhas esperadas sem esconder defeitos inesperados.

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso