# Desenvolvendo com asyncio

A programação assíncrona é diferente da programação “sequencial” clássica.

Esta página lista erros e armadilhas comuns e explica como evitá-los.

## Modo de Depuração

Por padrão, o asyncio é executado em modo de produção. Para facilitar o desenvolvimento, o asyncio fornece um modo de depuração.

Há várias maneiras de habilitar o modo de depuração do asyncio:

- Definindo a variável de ambiente `PYTHONASYNCIODEBUG` como `1`.

- Usando o Modo de Desenvolvimento do Python.

- Passando `debug=True` para `asyncio.run()`.

- Chamando `loop.set_debug()`.

Além de habilitar o modo de depuração, considere também:

- setting the log level of the asyncio logger to `logging.DEBUG`, for example the following snippet of code can be run at startup of the application:

      logging.basicConfig(level=logging.DEBUG)

- configurando o módulo `warnings` para exibir avisos `ResourceWarning`. Uma forma de fazer isso é usando a opção de linha de comando: `-W` `default`.

Quando o modo de depuração está habilitado:

- Muitas APIs do asyncio que não são thread-safe (como os métodos `loop.call_soon()` e `loop.call_at()`) levantam uma exceção se forem chamadas a partir de uma thread incorreta.

- O tempo de execução de um seletor de E/S é registrado se demorar muito para executar a operação E/S.

- Funções de retorno demorando mais do que 100 milissegundos são registradas. O atributo `loop.slow_callback_duration` pode ser usado para definir a duração de execução mínima em segundos para se considerada “devagar”.

## Concorrência e Múltiplas Threads

Um laço de eventos é executado em uma thread (geralmente a thread principal) e executa todos as funções de retorno e Tasks nessa mesma thread. Enquanto uma Task está em execução no laço de eventos, nenhuma outra Task pode ser executada na mesma thread. Quando uma Task executa uma expressão `await`, a Task em execução é suspensa, e o laço de eventos passa a executar a próxima Task.

Para agendar uma callback de outra thread do SO, o método `loop.call_soon_threadsafe()` deve ser usado. Exemplo:

    loop.call_soon_threadsafe(callback, *args)

Quase todos os objetos do asyncio não são seguros para threads, o que normalmente não é um problema, a menos que exista código que funcione com eles fora de uma Task ou de um callback. Se houver a necessidade de esse tipo de código chamar uma API de baixo nível do asyncio, o método `loop.call_soon_threadsafe()` deve ser usado, por exemplo:

    loop.call_soon_threadsafe(fut.cancel)

Para agendar um objeto corrotina a partir de uma thread diferente do sistema operacional, a função `run_coroutine_threadsafe()` deve ser usada. Ela retorna um `concurrent.futures.Future`. para acessar o resultado:

    async def coro_func():
         return await asyncio.sleep(1, 42)

    # Later in another OS thread:

    future = asyncio.run_coroutine_threadsafe(coro_func(), loop)
    # Wait for the result:
    result = future.result()

Para manipular sinais, o laço de eventos deve ser executado na thread principal.

O método `loop.run_in_executor()` pode ser usado com um `concurrent.futures.ThreadPoolExecutor` ou `InterpreterPoolExecutor` para executar o código de bloqueio em uma thread de sistema operacional diferente, sem bloquear a thread de sistema operacional na qual o laço de eventos é executado.

There is currently no way to schedule coroutines or callbacks directly from a different process (such as one started with `multiprocessing`). The Métodos do laço de eventos section lists APIs that can read from pipes and watch file descriptors without blocking the event loop. In addition, asyncio’s Subprocess APIs provide a way to start a process and communicate with it from the event loop. Lastly, the aforementioned `loop.run_in_executor()` method can also be used with a `concurrent.futures.ProcessPoolExecutor` to execute code in a different process.

## Executando código bloqueante

O código bloqueante (vinculada à CPU) não deve ser chamado diretamente. Por exemplo, se uma função executa um cálculo pesado de CPU durante 1 segundo, todas as Tasks concorrentes do asyncio e as operações de E/S serão atrasadas por 1 segundo.

Um executor pode ser usado para executar uma tarefa em uma thread diferente, incluindo em um interpretador diferente, ou até mesmo em um processo diferente, para evitar bloquear a thread do sistema operacional onde o laço de eventos está sendo executado. Consulte o método `loop.run_in_executor()` para mais detalhes.

## Gerando logs

asyncio usa o módulo `logging` e todo registro é feito via o registrador `"asyncio"`.

O nível padrão de log é `logging.INFO`, que pode ser facilmente ajustado:

    logging.getLogger("asyncio").setLevel(logging.WARNING)

Logging de rede pode bloquear o laço de eventos. Recomenda-se usar uma thread separada para lidar com os logs ou usar E/S não bloqueante. Por exemplo, veja Dealing with handlers that block.

## Detectar corrotinas nunca aguardadas

Quando uma função de corrotina é chamada, mas não é aguardada (por exemplo, `coro()` em vez de `await coro()`) ou a corrotina não é agendada com `asyncio.create_task()`, o asyncio emitirá um `RuntimeWarning`:

    import asyncio

    async def test():
        print("never scheduled")

    async def main():
        test()

    asyncio.run(main())

Saída:

    test.py:7: RuntimeWarning: coroutine 'test' was never awaited
      test()

Saída no modo de depuração:

    test.py:7: RuntimeWarning: coroutine 'test' was never awaited
    Coroutine created at (most recent call last)
      File "../t.py", line 9, in <module>
        asyncio.run(main(), debug=True)

      < .. >

      File "../t.py", line 7, in main
        test()
      test()

A correção usual é aguardar a corrotina ou chamar a função `asyncio.create_task()`:

    async def main():
        await test()

## Detectar exceções nunca recuperadas

Se um `Future.set_exception()` é chamado, mas o objeto Future nunca é aguardado, a exceção nunca será propagada para o código do usuário. Nesse caso, o asyncio emitirá uma mensagem de log quando o objeto Future for coletado pela coleta de lixo.

Exemplo de uma exceção não tratada:

    import asyncio

    async def bug():
        raise Exception("not consumed")

    async def main():
        asyncio.create_task(bug())

    asyncio.run(main())

Saída:

    Task exception was never retrieved
    future: <Task finished coro=<bug() done, defined at test.py:3>
      exception=Exception('not consumed')>

    Traceback (most recent call last):
      File "test.py", line 4, in bug
        raise Exception("not consumed")
    Exception: not consumed

Enable the debug mode to get the traceback where the task was created:

    asyncio.run(main(), debug=True)

Saída no modo de depuração:

    Task exception was never retrieved
    future: <Task finished coro=<bug() done, defined at test.py:3>
        exception=Exception('not consumed') created at asyncio/tasks.py:321>

    source_traceback: Object created at (most recent call last):
      File "../t.py", line 9, in <module>
        asyncio.run(main(), debug=True)

    < .. >

    Traceback (most recent call last):
      File "../t.py", line 4, in bug
        raise Exception("not consumed")
    Exception: not consumed