8.6. Exceções¶
As exceções dentro de um script asyncio se comportam quase da mesma forma que no Python comum – elas se propagam pela cadeia de chamadas até que algo as capture. Quase porque as tarefas estão em execução paralela, de modo que o caminho “para cima” não é o caminho que criou a tarefa. Esta página aborda para onde vão as exceções em cada um dos formatos comuns.
8.6.1. Dentro de uma corrotina¶
Um try/except dentro de uma corrotina captura as exceções lançadas por qualquer coisa que ela awaita, da forma habitual:
async def fetch_with_retry(url):
for attempt in range(3):
try:
return await fetch(url)
except OSError as e:
last_error = e
raise last_error
Nada específico do asyncio aqui – a cláusula except vê as exceções lançadas dentro de fetch como se fosse uma chamada de função comum.
8.6.2. Em uma tarefa que a aplicação está aguardando¶
Quando uma corrotina executando como uma Task lança uma exceção, ela é armazenada na tarefa. Na próxima vez que algo awaita essa tarefa, a exceção é relançada no await
task = asyncio.create_task(may_fail())
try:
result = await task
except OSError:
log("may_fail failed")
O mesmo se aplica a asyncio.gather(). O comportamento padrão – um filho lança uma exceção, os outros são cancelados, a exceção se propaga para fora do gather – vem desse mecanismo.
8.6.3. Em uma tarefa que ninguém aguarda¶
Uma tarefa que ninguém nunca awaita é o caso que exige atenção. A exceção ainda acontece; o laço percebe que a tarefa terminou com uma exceção não tratada; mas não há nenhum await no qual ela possa aflorar. O comportamento padrão é imprimir um traceback através de sys.stderr e continuar a execução – o que é adequado para um diagnóstico não supervisionado, mas inadequado para uma aplicação que queria saber.
A correção certa costuma ser aguardar a tarefa. Seja diretamente, lembrando-se do handle e aguardando-o durante o desligamento, seja implicitamente por meio de gather() ou wait_for(). O padrão de “desligar uma aplicação” da página Timeouts e cancelamento trata desse caso para as tarefas de segundo plano de longa duração que um script típico cria.
8.6.4. Manipulador de exceções personalizado¶
Quando o silencioso traceback-e-continua não é suficiente, o laço expõe um gancho – Loop.set_exception_handler – que a aplicação pode sobrescrever para fazer outra coisa:
def handler(loop, context):
print("asyncio:", context.get("message"))
if "exception" in context:
sys.print_exception(context["exception"])
loop = asyncio.get_event_loop()
loop.set_exception_handler(handler)
O argumento context é um dicionário com as chaves 'message', 'exception' e 'future'. A exceção pode estar ausente em certos eventos do tipo aviso, e é por isso que o exemplo usa .get().
Os usos típicos são registrar a falha na flash, piscar um LED de erro ou escalar para um reinício por watchdog. A página controle do laço aborda toda a superfície dos ganchos do laço.
8.6.5. KeyboardInterrupt¶
Quando um script é interrompido externamente – geralmente pela IDE solicitando que ele pare – a solicitação chega dentro do script como um KeyboardInterrupt. Dentro de asyncio.run(), ela se propaga da mesma forma que qualquer outra exceção não tratada: main é cancelada, todas as tarefas que o laço está rastreando também são canceladas, e o KeyboardInterrupt é relançado para fora de asyncio.run(). As cláusulas finally são executadas no caminho de saída, de modo que o mesmo padrão de limpeza da página de cancelamento é o que lida com isso.