Trilha de aprendizado · Nível 13 · Tutorial 9

Aplicar tempos de espera a operações assíncronas

Ao concluir, você será capaz de delimitar o tempo de espera de uma operação ou de um conjunto de operações assíncronas e tratar o esgotamento desse prazo sem ocultar cancelamentos externos.

  • Nível: Avançado
  • Duração: 18 min
  • 8 passos
Aplicar tempos de espera a operações assíncronas

O que você vai percorrer

  1. Definir o alcance do orçamento de tempo Compare um prazo total compartilhado com limites renovados em cada operação. 2 min
  2. Delimitar um escopo com asyncio.timeout Use um único orçamento de tempo para operações sequenciais e trate o prazo esgotado no ponto correto. 3 min
  3. Limitar uma espera com asyncio.wait_for Use wait_for para limitar a espera por um aguardável e identifique qual tarefa recebe o cancelamento quando o prazo se esgota. 2 min
  4. Separar timeout, cancelamento externo e falha Identifique a origem de cada desfecho e preserve cancelamentos externos e falhas reais ao tratar prazos locais. 3 min
  5. Considerar o tempo de limpeza Entenda por que o prazo configurado pode expirar antes de a operação e seu tratamento de timeout serem efetivamente concluídos. 2 min
  6. Reconhecer os limites de um prazo cooperativo Diagnostique por que um timeout pode ser observado depois do prazo nominal — ou nem produzir TimeoutError — quando o laço fica bloqueado ou o cancelamento é suprimido. 2 min
  7. Aplicar um orçamento ao TaskGroup inteiro Posicione um orçamento compartilhado ao redor de um TaskGroup para abranger a execução e o encerramento de todas as tarefas relacionadas. 3 min
  8. Aplicar e verificar a política de tempo Conclua uma aplicação local que combina limites individuais e um orçamento compartilhado, preservando cancelamentos externos e falhas reais. 3 min

O que você vai aprender

  • Aplicar asyncio.timeout a um escopo de operações.
  • Usar asyncio.wait_for para aguardar uma operação com limite de tempo.
  • Distinguir timeout, cancelamento externo e falha da operação.
  • Explicar por que o encerramento pode ultrapassar o prazo nominal.

Antes de começar

  • Coordenar tarefas com asyncio.TaskGroup

Passo 1 de 8

Definir o alcance do orçamento de tempo

Compare um prazo total compartilhado com limites renovados em cada operação.

Prazo é uma política, não uma pausa

O que um tempo de espera decide

Um tempo de espera define uma política: se o prazo for esgotado antes de a operação terminar, o programa deve interromper a espera e seguir o tratamento previsto. Ele não é uma pausa colocada antes de executar o trabalho.

A primeira decisão é o alcance desse prazo: ele vale para o conjunto inteiro de etapas ou recomeça a cada chamada?

Duas fronteiras possíveis

Observe a diferença entre limitar a jornada inteira e dar um relógio novo a cada etapa.

Comparação de duas linhas do tempo: três operações sequenciais sob um único prazo compartilhado e as mesmas operações com um prazo independente para cada chamada.

No orçamento compartilhado, o tempo já gasto pela primeira etapa reduz o que resta para as seguintes.

Duas políticas para a mesma sequência

Exemplo

Três etapas sequenciais

Imagine três operações aguardadas em sequência:

  • consultar cadastro: 1,2 s
  • buscar pedidos: 1,1 s
  • montar resposta: 1,0 s

Orçamento compartilhado de 3 s: as duas primeiras consomem 2,3 s; restam cerca de 0,7 s para montar a resposta. O requisito é: “a sequência toda deve caber em 3 s”.

Limite independente de 3 s por chamada: cada etapa pode usar até 3 s. Como os limites se renovam, a sequência pode chegar perto de 9 s sem violar nenhum limite individual. Isso não estabelece, por si só, um teto total de 3 s.

Dica

Pergunta de projeto

Pergunte: “Qual trabalho precisa terminar dentro deste prazo?” Se a resposta for o fluxo completo, escolha uma política de orçamento compartilhado. Se cada dependência tiver sua própria tolerância, limites independentes podem ser adequados.

Escolha a política pelo requisito

Orçamento do fluxo

Um endpoint executa, em sequência, validação, consulta e formatação. O requisito é responder em até 2 segundos no total. Qual política representa esse requisito?

Passo 2 de 8

Delimitar um escopo com asyncio.timeout

Use um único orçamento de tempo para operações sequenciais e trate o prazo esgotado no ponto correto.

Um contexto, um orçamento compartilhado

Delimite o bloco inteiro

Use async with asyncio.timeout(segundos): para aplicar um orçamento de tempo a tudo que for aguardado dentro do bloco. As operações são sequenciais, mas compartilham o mesmo prazo: o tempo gasto na primeira reduz o disponível para a segunda.

O contexto não cria uma tarefa extra. Quando o prazo termina, ele solicita o cancelamento da tarefa corrente, que está executando o bloco.

O prazo cobre as duas esperas

As duas operações consomem o mesmo intervalo delimitado pelo contexto.

Diagrama de uma única tarefa contendo duas operações sequenciais dentro de uma faixa de tempo compartilhada; a primeira ocupa parte da faixa e a segunda alcança o limite, onde aparece um sinal de cancelamento na tarefa corrente.

Um único prazo envolve as duas operações; não há reinício automático do contador entre elas.

Script local completo

Execute com Python 3.12 ou superior

Crie um arquivo, por exemplo escopo_timeout.py, e execute python escopo_timeout.py. O exemplo usa somente a biblioteca padrão e asyncio.sleep() para simular operações que suspendem cooperativamente.

Um timeout para duas operações

python
import asyncio


async def operacao(nome: str, duracao: float) -> None:
    print(f"início: {nome}")
    await asyncio.sleep(duracao)
    print(f"fim: {nome}")


async def main() -> None:
    try:
        async with asyncio.timeout(1.0):
            await operacao("primeira", 0.4)
            await operacao("segunda", 0.8)
    except TimeoutError:
        print("prazo do escopo esgotado")
    else:
        print("escopo concluído no prazo")


asyncio.run(main())

Exemplo

Leitura do fluxo

A primeira operação consome cerca de 0,4 s. Restam cerca de 0,6 s para a segunda, que foi simulada para durar 0,8 s. Portanto, ela não chega ao seu fim e o programa imprime prazo do escopo esgotado.

Para observar o fluxo de sucesso, reduza a duração da segunda operação para 0.5. Nesse caso, as duas terminam e o bloco else é executado.

Capture fora do contexto

A conversão acontece na saída

Ao atingir o prazo, o contexto solicita o cancelamento da tarefa corrente. Ao sair de async with, ele reconhece o cancelamento causado por seu próprio prazo e o apresenta como a exceção embutida TimeoutError.

Por isso, coloque except TimeoutError fora do async with. Dentro do bloco, o fluxo está sendo interrompido pelo cancelamento; é na saída do gerenciador que o resultado de prazo esgotado fica disponível como TimeoutError.

Dica

Não importe uma exceção desnecessariamente

Neste caso, use except TimeoutError:. Em Python 3.12, TimeoutError é a exceção embutida apropriada para tratar o esgotamento do contexto.

Pratique a fronteira de tratamento

Complete o tratamento

Complete a linha que deve envolver o contexto para capturar corretamente o prazo esgotado:

async def main() -> None:
    ___:
        async with asyncio.timeout(0.5):
            await operacao("A", 0.2)
            await operacao("B", 0.6)
    except TimeoutError:
        print("prazo esgotado")

Passo 3 de 8

Limitar uma espera com asyncio.wait_for

Use wait_for para limitar a espera por um aguardável e identifique qual tarefa recebe o cancelamento quando o prazo se esgota.

A fronteira é o aguardável

Limite uma espera específica

Use await asyncio.wait_for(aguardavel, timeout) quando o prazo deve valer para a espera por um aguardável. O valor de timeout é dado em segundos.

Ao esgotar o prazo, wait_for solicita o cancelamento da operação aguardada — não da tarefa chamadora diretamente. Depois, aguarda o encerramento cooperativo dessa operação. Se ela encerra sem outra falha, a chamada resulta em TimeoutError.

Dois limites, dois alvos

Compare o alvo direto do cancelamento em cada política.

Diagrama comparando asyncio.timeout e asyncio.wait_for. No timeout, um relógio envia cancelamento à tarefa corrente que executa o bloco. No wait_for, a tarefa chamadora permanece ativa enquanto o relógio envia cancelamento à operação aguardada em uma tarefa separada.

asyncio.timeout delimita a tarefa corrente; asyncio.wait_for delimita a espera por um aguardável.

Um exemplo autocontido

Esperar no máximo 0,2 segundo

Execute com Python 3.12 ou superior.

python
import asyncio


async def buscar_dados() -> str:
    await asyncio.sleep(1)
    return "dados recebidos"


async def main() -> None:
    try:
        resultado = await asyncio.wait_for(buscar_dados(), timeout=0.2)
        print(resultado)
    except TimeoutError:
        print("A busca ultrapassou o prazo")


asyncio.run(main())

O que ocorre aqui

buscar_dados() produz uma corrotina. Ao recebê-la, wait_for a agenda automaticamente como uma Task para poder acompanhá-la e cancelá-la se necessário.

Como a busca simula 1 segundo e o limite é 0,2, ela recebe o pedido de cancelamento. Após seu encerramento, wait_for lança a exceção embutida TimeoutError, capturada no try.

Dica

Se você já tem uma Task

Se o aguardável já for uma Task, como uma criada com asyncio.create_task(...), wait_for a utiliza diretamente: não cria outra Task para a mesma operação.

Associe a política ao seu alcance

Fronteira e alvo do cancelamento

Associe cada situação à descrição correta.

Toque em um item e depois no par correspondente.

Escolha a fronteira certa

Uma regra de decisão

Escolha wait_for quando a pergunta for: “quanto tempo posso esperar por este aguardável?”. Esse aguardável pode representar uma única operação ou trabalho composto.

Escolha um escopo asyncio.timeout quando várias esperas e trechos do bloco precisam compartilhar um único orçamento. Nos dois casos, o prazo delimita uma política de espera; o encerramento cooperativo é observado antes de a chamada retornar.

Decisão de API

Você já possui uma Task que representa uma consulta e quer limitar apenas o tempo de espera por ela, sem aplicar automaticamente o mesmo prazo ao restante do código chamador. Qual escolha expressa essa fronteira?

Passo 4 de 8

Separar timeout, cancelamento externo e falha

Identifique a origem de cada desfecho e preserve cancelamentos externos e falhas reais ao tratar prazos locais.

Três origens, três significados

Não rotule tudo como timeout

Um prazo local é apenas uma das formas de uma espera terminar. Ao observar uma exceção, preserve sua origem:

  • TimeoutError do contexto ou de wait_for: a política local de tempo esgotou.
  • asyncio.CancelledError: outra parte do programa cancelou a tarefa; esse sinal deve continuar subindo.
  • Outra exceção: a própria operação falhou e sua falha deve permanecer visível.

Execute e analise esses cenários separadamente. Não tente decidir a origem com base em qual evento “chegou primeiro”.

Fluxo de tratamento por origem

A fronteira de tratamento deve reconhecer somente o prazo que ela própria definiu.

Diagrama com uma corrotina central e três saídas separadas: timeout local vira TimeoutError na saída do contexto, cancelamento externo continua como CancelledError e falha interna continua como exceção da operação.

Timeout local, cancelamento externo e falha da operação exigem tratamentos diferentes.

Capture apenas o que você quer resolver

Timeout local sem ocultar outros desfechos

Este script usa uma operação simulada. Execute um modo por vez, alterando a chamada em main().

python
import asyncio


async def operacao(modo: str) -> str:
    await asyncio.sleep(0.1)

    if modo == "falha":
        raise ValueError("resposta inválida")
    if modo == "timeout_proprio":
        raise TimeoutError("prazo informado pela operação")

    await asyncio.sleep(1)
    return "resultado"


async def com_prazo_local(modo: str) -> None:
    try:
        async with asyncio.timeout(0.2):
            resultado = await operacao(modo)
            print(resultado)
    except TimeoutError as erro:
        print(f"timeout observado: {erro}")


async def main() -> None:
    await com_prazo_local("falha")


asyncio.run(main())

Atenção

TimeoutError também pode vir da operação

O except TimeoutError acima observa tanto o prazo do contexto quanto um TimeoutError lançado por operacao. Portanto, o tipo isolado não prova a origem. Se essa diferença importar para sua regra de negócio, modele a falha da operação com uma exceção própria ou organize fronteiras em que a origem seja conhecida.

Limpeza ao receber cancelamento externo

Use este padrão somente quando precisar registrar ou liberar recursos antes de propagar o cancelamento.

python
async def operacao_com_limpeza() -> None:
    try:
        await asyncio.sleep(10)
    except asyncio.CancelledError:
        print("cancelamento externo observado; iniciando limpeza")
        # Libere aqui os recursos que pertencem a esta operação.
        raise  # Nunca converta em sucesso nem em TimeoutError.

Classifique antes de tratar

Cancelamento externo

Uma tarefa chama cancel() sobre a tarefa que executa com_prazo_local, antes de o prazo local expirar. Qual tratamento preserva corretamente o significado do evento?

Justifique a fronteira correta

Falha não é prazo

Por que except BaseException seguido de uma mensagem como “timeout” é incorreto em uma função que usa asyncio.timeout? Explique o que deve acontecer com um CancelledError externo e com um ValueError produzido pela operação.

Escreva pelo menos 80 caracteres (0/80).

Passo 5 de 8

Considerar o tempo de limpeza

Entenda por que o prazo configurado pode expirar antes de a operação e seu tratamento de timeout serem efetivamente concluídos.

O prazo dispara o encerramento; não o encurta à força

Três instantes diferentes

Quando um prazo se esgota, há três momentos distintos: (1) o prazo nominal vence; (2) o cancelamento é solicitado; (3) a operação termina seu encerramento. Se ela executa um finally, essa limpeza ocorre depois da solicitação e antes de o chamador observar TimeoutError.

Portanto, ultrapassar o valor configurado não prova que o timeout falhou: ele pode estar aguardando o encerramento cooperativo.

Linha do tempo do encerramento

A limpeza vem após a expiração e antes da exceção chegar ao código que aguarda.

Diagrama de linha do tempo mostrando prazo nominal, pedido de cancelamento, fase de limpeza assíncrona e observação final de TimeoutError.

O intervalo de limpeza aumenta a duração observada, embora o prazo já tenha vencido.

Observe a limpeza em uma operação limitada

Execute localmente

Salve o script abaixo em um arquivo, por exemplo limpeza_timeout.py, e execute com Python 3.12 ou superior. As mensagens permitem observar a sequência; não use os valores de duração como uma medição exata.

wait_for aguarda o encerramento

python
import asyncio


async def operacao() -> None:
    try:
        print("operação: iniciada")
        await asyncio.sleep(2)
    finally:
        print("operação: início da limpeza")
        await asyncio.sleep(0.3)
        print("operação: fim da limpeza")


async def main() -> None:
    try:
        await asyncio.wait_for(operacao(), timeout=0.1)
    except TimeoutError:
        print("chamador: timeout observado")


asyncio.run(main())

Dica

O que observar

A mensagem de início da limpeza aparece após o cancelamento solicitado pelo prazo. A mensagem chamador: timeout observado só aparece depois de operação: fim da limpeza, pois wait_for espera o encerramento da operação cancelada.

A mesma ideia em um escopo timeout

A saída do contexto também espera

Com asyncio.timeout, o prazo cancela a tarefa corrente. Ao sair do async with, essa tarefa ainda precisa atravessar o finally da operação que estava aguardando. Só então o contexto converte seu próprio cancelamento em TimeoutError para o tratamento externo.

Timeout aplicado ao escopo

python
import asyncio


async def operacao() -> None:
    try:
        print("operação: iniciada")
        await asyncio.sleep(2)
    finally:
        print("operação: início da limpeza")
        await asyncio.sleep(0.3)
        print("operação: fim da limpeza")


async def main() -> None:
    try:
        async with asyncio.timeout(0.1):
            await operacao()
    except TimeoutError:
        print("chamador: timeout observado")


asyncio.run(main())

Atenção

Não conclua cedo demais

Em ambos os exemplos, o prazo não é uma garantia de retorno no instante exato configurado. Neste cenário cooperativo e sem outra falha, ele inicia o cancelamento no prazo e a limpeza prolonga a conclusão observada.

Verifique a sequência e explique o resultado

Ordene os eventos

Em um cenário cooperativo com finally assíncrono, coloque os eventos na ordem correta.

  1. O cancelamento da operação ou da tarefa corrente é solicitado.
  2. O bloco finally realiza e conclui a limpeza.
  3. O prazo nominal se esgota.
  4. O chamador trata TimeoutError.

Interprete sua execução

Após executar um dos scripts, por que a mensagem de timeout aparece depois das mensagens de limpeza, mesmo com um prazo de 0,1 segundo?

Escreva pelo menos 60 caracteres (0/60).

Passo 6 de 8

Reconhecer os limites de um prazo cooperativo

Diagnostique por que um timeout pode ser observado depois do prazo nominal — ou nem produzir TimeoutError — quando o laço fica bloqueado ou o cancelamento é suprimido.

Um prazo depende da cooperação do laço

O relógio não interrompe código à força

Um timeout configura um instante para solicitar cancelamento, mas o laço de eventos precisa voltar a executar para processar esse instante e entregar o cancelamento. Portanto, um prazo pequeno não é uma garantia de interrupção rígida naquele milissegundo.

Há três situações visualmente parecidas — o resultado aparece depois do prazo —, mas com causas diferentes: limpeza cooperativa, bloqueio do laço e supressão de CancelledError.

Três motivos para ultrapassar o prazo nominal

Diagrama com três linhas do tempo: uma mostra prazo seguido de limpeza cooperativa, outra mostra um longo bloco síncrono atravessando o prazo e a terceira mostra um sinal de cancelamento seguido de continuação da operação.

Limpeza adia a conclusão após o cancelamento; bloqueio atrasa a própria entrega do cancelamento; supressão faz a operação seguir ou retornar em vez de propagar o cancelamento.

Bloquear o laço atrasa o timeout

Uma chamada síncrona ocupa a thread do laço

time.sleep() não oferece um ponto de suspensão ao asyncio. Enquanto ela executa, o laço não processa o vencimento do timeout. No exemplo, o cancelamento só pode ser percebido depois que a chamada síncrona termina e a tarefa volta a fazer await.

Prazo de 0,05 s observado só após o bloqueio

Execute como um script local com Python 3.12 ou superior.

python
import asyncio
import time


async def main() -> None:
    inicio = time.perf_counter()

    try:
        async with asyncio.timeout(0.05):
            time.sleep(0.20)  # Bloqueia o laço de eventos.
            await asyncio.sleep(0)  # O laço finalmente pode processar o prazo.
    except TimeoutError:
        decorrido = time.perf_counter() - inicio
        print(f"Timeout observado após cerca de {decorrido:.2f} s")


asyncio.run(main())

Atenção

Não confunda atraso com falha da política

O timeout continua sendo uma política de cancelamento cooperativo. Ele não consegue interromper uma chamada síncrona demorada nem processamento intenso sem pontos de suspensão. Para código bloqueante, será necessário escolher outra integração apropriada em um próximo tópico.

Não transforme cancelamento em sucesso

Suprimir CancelledError muda o desfecho

Se a operação capturar asyncio.CancelledError e retornar normalmente, o contexto de timeout pode terminar sem o TimeoutError esperado. Isso oculta o pedido de cancelamento e permite que um resultado improvisado pareça sucesso.

Quando precisar apenas observar ou limpar recursos, use finally ou capture CancelledError e relance-o com raise.

Exemplo de supressão incorreta

Aqui, o retorno dentro de except impede a propagação do cancelamento.

python
import asyncio


async def operacao() -> str:
    try:
        await asyncio.sleep(0.20)
        return "resultado normal"
    except asyncio.CancelledError:
        print("Cancelamento recebido, mas suprimido indevidamente")
        return "resultado improvisado"  # Não faça isso para ocultar o cancelamento.


async def main() -> None:
    try:
        async with asyncio.timeout(0.05):
            resultado = await operacao()
            print(resultado)
    except TimeoutError:
        print("prazo esgotado")


asyncio.run(main())

Dica

Correção mínima

Se houver limpeza específica, mantenha-a em finally. Se capturar asyncio.CancelledError para registrar algo, termine o bloco com raise para preservar o cancelamento.

Diagnóstico do atraso

Associe cada situação à causa principal

Relacione cada cenário ao motivo mais direto para o comportamento observado.

Toque em um item e depois no par correspondente.

Prazo rígido?

asyncio.timeout(0.05) garante que qualquer código dentro do bloco pare exatamente após 0,05 segundo.

Passo 7 de 8

Aplicar um orçamento ao TaskGroup inteiro

Posicione um orçamento compartilhado ao redor de um TaskGroup para abranger a execução e o encerramento de todas as tarefas relacionadas.

Um prazo para o ciclo inteiro do grupo

O timeout envolve também a saída do grupo

Posicione asyncio.timeout(...) por fora de asyncio.TaskGroup(). Assim, todas as tarefas criadas e a espera feita ao sair do grupo consomem o mesmo orçamento.

Quando o prazo vence, o contexto solicita o cancelamento da tarefa corrente. Ao sair do TaskGroup, esse cancelamento faz o grupo cancelar as tarefas ainda ativas e esperar que elas terminem a limpeza. Se esse encerramento cooperar e não houver outra falha, o TimeoutError é observado fora dos dois contextos.

Fluxo do cancelamento

O prazo externo cobre a execução e o encerramento estruturado.

Diagrama mostrando um cronômetro envolvendo um TaskGroup com três tarefas; uma tarefa já concluída, uma terminando normalmente e uma recebendo cancelamento, passando por limpeza antes de o fluxo chegar ao manipulador de TimeoutError externo.

A saída do TaskGroup precisa ficar dentro do asyncio.timeout: é nela que o grupo espera as tarefas restantes.

Script local: prazo compartilhado e limpeza

Execute em Python 3.12 ou superior

Crie um arquivo, por exemplo grupo_com_prazo.py, copie o código e execute python grupo_com_prazo.py. Os tempos são apenas uma simulação controlada: não dependa da ordem exata das mensagens entre tarefas.

Timeout ao redor do TaskGroup

python
import asyncio


async def trabalho(nome: str, duracao: float) -> None:
    try:
        print(f"{nome}: iniciou")
        await asyncio.sleep(duracao)
        print(f"{nome}: concluiu o trabalho")
    finally:
        print(f"{nome}: iniciou a limpeza")
        await asyncio.sleep(0.10)
        print(f"{nome}: terminou a limpeza")


async def main() -> None:
    try:
        async with asyncio.timeout(0.25):
            async with asyncio.TaskGroup() as grupo:
                grupo.create_task(trabalho("rápida", 0.05))
                grupo.create_task(trabalho("lenta", 1.00))
    except TimeoutError:
        print("orçamento do grupo esgotado")


asyncio.run(main())

Exemplo

O que observar

A tarefa lenta não termina seu trabalho de 1 segundo: ela recebe cancelamento quando o orçamento se esgota. Ainda assim, sua cláusula finally executa e aguarda a limpeza. Por isso, a mensagem orçamento do grupo esgotado pode aparecer depois de 0,25 segundo; o prazo nominal não elimina o tempo necessário para encerrar cooperativamente.

Complete a fronteira correta

Orçamento compartilhado

Complete a linha que deve vir imediatamente antes de async with asyncio.TaskGroup() as grupo:

____

Timeout não apaga outras falhas

Preveja o desfecho

No script, suponha que todas as limpezas terminem normalmente e nenhuma tarefa lance outra exceção. Qual resultado o except externo observa quando o prazo vence?

Atenção

Não reduza toda exceção a prazo esgotado

Capture TimeoutError na fronteira da política de prazo, mas não use uma captura ampla para rotular falhas das tarefas como timeout. Um ExceptionGroup vindo do TaskGroup continua sendo evidência de falhas que exigem tratamento próprio.

Passo 8 de 8

Aplicar e verificar a política de tempo

Conclua uma aplicação local que combina limites individuais e um orçamento compartilhado, preservando cancelamentos externos e falhas reais.

Escolha a fronteira certa

Duas políticas, dois alcances

Use asyncio.wait_for(...) quando quiser limitar a espera por um aguardável específico. Ao vencer o prazo, a operação aguardada recebe o pedido de cancelamento.

Use asyncio.timeout(...) ao redor de um bloco — inclusive de um TaskGroup — quando várias operações devem consumir o mesmo orçamento de tempo. Nesse caso, a tarefa corrente recebe o cancelamento; o encerramento estruturado alcança as tarefas do grupo.

Em ambos os casos, TimeoutError deve ser tratado na fronteira da política local. CancelledError externo e falhas como RuntimeError não devem ser rotulados como timeout.

Fronteiras de cancelamento

Compare o alvo do cancelamento e o alcance do orçamento.

Diagrama lado a lado: wait_for envolve uma operação e envia cancelamento para ela; timeout envolve um TaskGroup e cancela a tarefa que controla o grupo, levando ao encerramento das tarefas filhas.

wait_for delimita uma espera; timeout delimita um escopo compartilhado.

Execute a aplicação local

Script autocontido

Em um arquivo chamado politica_tempo.py, cole o código completo abaixo e execute python politica_tempo.py com Python 3.12 ou superior. Os atrasos são apenas simulações: observe as mensagens e as exceções, sem depender de valores exatos de duração.

politica_tempo.py

python
import asyncio


async def operacao(
    nome: str,
    demora: float,
    *,
    falha: bool = False,
    limpeza: float = 0,
) -> str:
    print(f"{nome}: início")
    try:
        await asyncio.sleep(demora)
        if falha:
            raise RuntimeError(f"{nome}: falha própria da operação")
        print(f"{nome}: resultado pronto")
        return nome
    finally:
        print(f"{nome}: início da limpeza")
        if limpeza:
            await asyncio.sleep(limpeza)
        print(f"{nome}: fim da limpeza")


async def limite_individual() -> None:
    print("\n--- limite individual ---")
    try:
        resultado = await asyncio.wait_for(
            operacao("consulta-lenta", 0.20, limpeza=0.05),
            timeout=0.05,
        )
        print(f"resultado: {resultado}")
    except TimeoutError:
        print("chamador: prazo individual esgotado")


async def falha_da_operacao() -> None:
    print("\n--- falha da operação ---")
    try:
        await asyncio.wait_for(
            operacao("consulta-com-falha", 0.01, falha=True),
            timeout=1,
        )
    except RuntimeError as erro:
        print(f"chamador: falha preservada: {erro}")


async def cancelamento_externo() -> None:
    print("\n--- cancelamento externo ---")
    tarefa = asyncio.create_task(
        asyncio.wait_for(operacao("consulta-cancelada", 1), timeout=2)
    )
    await asyncio.sleep(0.02)
    tarefa.cancel()

    try:
        await tarefa
    except asyncio.CancelledError:
        print("chamador: cancelamento externo continuou se propagando")


async def orcamento_do_grupo() -> None:
    print("\n--- orçamento compartilhado do grupo ---")
    try:
        async with asyncio.timeout(0.05):
            async with asyncio.TaskGroup() as grupo:
                grupo.create_task(operacao("rápida", 0.01))
                grupo.create_task(
                    operacao("lenta-com-limpeza", 0.20, limpeza=0.05)
                )
    except TimeoutError:
        print("chamador: orçamento do grupo esgotado")


async def main() -> None:
    await limite_individual()
    await falha_da_operacao()
    await cancelamento_externo()
    await orcamento_do_grupo()


asyncio.run(main())

Verifique os desfechos

O que deve ser observável

A execução deve mostrar, em cenários separados:

  • consulta-lenta inicia a limpeza antes de o chamador informar o timeout individual;
  • a RuntimeError da segunda operação chega ao chamador como falha, não como TimeoutError;
  • o cancelamento chamado por tarefa.cancel() continua como CancelledError até o chamador que o observa;
  • no grupo, a tarefa rápida pode concluir, enquanto a lenta é encerrada e limpa antes da mensagem de orçamento esgotado.

A ordem entre mensagens de tarefas concorrentes pode variar. O ponto importante é que a limpeza termina antes da observação final do timeout.

Justifique as fronteiras

Após executar o arquivo, explique: qual política foi usada para cada cenário, qual exceção chegou ao chamador e como a limpeza afetou o encerramento.

Escreva pelo menos 180 caracteres (0/180).

Síntese final

Resumo

Critérios para revisar uma política de tempo

Antes de aplicar um prazo, defina a fronteira que realmente precisa ser limitada.

  • Alcance: wait_for limita a espera por um aguardável; timeout dá um orçamento a um escopo.
  • Alvo: wait_for solicita cancelamento da operação aguardada; timeout cancela a tarefa corrente ao vencer.
  • Exceção: trate o TimeoutError da política local, preserve falhas próprias e não suprima CancelledError externo.
  • Encerramento: o prazo nominal é o instante do pedido de cancelamento; a limpeza cooperativa pode prolongar a duração observada.
  • Evidência: registre início e fim de limpeza e verifique os cenários separadamente, sem assumir uma ordem rígida entre tarefas concorrentes.

Tutorial concluído

Parabéns! Você concluiu: Aplicar tempos de espera a operações assíncronas

Agora você consegue escolher entre um limite individual e um orçamento compartilhado, tratar o timeout na fronteira correta e preservar cancelamentos externos e falhas da operação.

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