2.32. Corrotinas

Uma corrotina é uma função que pode pausar no meio do caminho e mais tarde retomar de onde parou, com todas as suas variáveis locais intactas. Ela espelha o padrão dos geradores – um corpo de função que pode ser suspenso e retomado – com uma mudança em quem comanda cada retomada.

A sintaxe do Python para escrever uma corrotina é o par de palavras-chave async / await. async marca a função como uma corrotina; await marca os pontos dentro dela onde é permitido pausar.

2.32.1. Definindo uma corrotina

Uma função definida com async def é uma função corrotina. Chamá-la não executa o corpo; ela retorna um objeto corrotina que ainda não começou:

async def greet(name):
    print("hello,", name)

coro = greet("Alice")        # body NOT run yet

O objeto corrotina está pausado bem no início da função. Algo precisa comandá-lo para fazer o corpo rodar – esse algo é um laço de eventos (event loop), um componente de tempo de execução. O MicroPython traz um no módulo asyncio. Por enquanto, trate a corrotina como “pronta para rodar, esperando por um comandante”.

2.32.2. Pausando dentro de uma corrotina

Dentro de uma corrotina, uma expressão await suspende a execução até que o valor aguardado esteja pronto:

async def fetch_and_log():
    data = await read_sensor()
    print("got:", data)

Quando o corpo chega a await read_sensor(), a corrotina devolve o controle a quem quer que a esteja executando. Quando read_sensor() termina, o comandante retoma a corrotina na linha seguinte, com o resultado vinculado a data.

await só é válido dentro de uma corrotina. Usá-lo em uma função comum é um erro de sintaxe.

2.32.3. Relação com os geradores

Corrotinas e geradores compartilham o mesmo mecanismo subjacente. A diferença está em quem aciona cada retomada:

  • Um gerador produz valores; o consumidor puxa o próximo com next() ou iterando.

  • Uma corrotina cede controle; um laço de eventos agenda a retomada quando a operação aguardada está pronta.

Se o aperto de mão do yield dos geradores faz sentido, o aperto de mão das corrotinas é a mesma ideia – apenas comandado por um laço de eventos em vez de um laço for.

Um laço de eventos (event loop) é um pequeno despachante que mantém uma lista de corrotinas esperando por algo (um timer, um evento de rede, outra corrotina terminando). A cada iteração ele escolhe uma corrotina cuja espera foi satisfeita, a retoma até o próximo await, então registra pelo que aquela corrotina está esperando agora e passa para outra que esteja pronta. O resultado são muitas tarefas progredindo concorrentemente em uma única thread – cada corrotina cede o controle voluntariamente em seus pontos await, e o laço preenche esses momentos com quaisquer outras corrotinas que estejam prontas para progredir.

Por baixo dos panos, await e yield usam o mesmo recurso de tempo de execução do Python para suspender e retomar uma função. As palavras-chave diferem porque a convenção em torno delas difere: yield devolve um valor a um consumidor que o puxa com next(); await entrega o controle a um laço de eventos que agenda a retomada quando a operação aguardada está pronta. async / await é essencialmente uma sintaxe mais nova para o padrão de corrotina – bibliotecas mais antigas construíam corrotinas diretamente sobre o maquinário de geradores, usando yield from (apresentado em Iteradores e geradores) para delegar a suspensão entre corrotinas.

2.32.4. Corrotinas precisam de um comandante

Uma corrotina é inerte sem um tempo de execução para comandá-la. Defini-la é tranquilo; executá-la requer um laço de eventos. O módulo asyncio do MicroPython fornece esse laço de eventos. A seção Asyncio cobre como iniciar o laço, agendar corrotinas nele, compartilhar estado entre elas com locks e eventos, lidar com cancelamento e timeouts, e moldar uma aplicação real em torno das palavras-chave async / await apresentadas aqui.