8.7. Eventos

asyncio.Event é a primitiva de sinalização mais simples que o módulo fornece. Uma coroutine define um evento quando algo aconteceu; qualquer número de outras coroutines aguarda que o evento seja definido antes de continuar. Não há carga útil – um evento é um booleano que passa a ativo e permanece assim até que algo o limpe.

8.7.1. A forma básica

import asyncio

async def waiter(evt):
    print("waiting")
    await evt.wait()
    print("got signal")

async def main():
    evt = asyncio.Event()
    asyncio.create_task(waiter(evt))
    await asyncio.sleep(1)
    evt.set()
    await asyncio.sleep(0)

asyncio.run(main())

A tarefa de espera corre, atinge await evt.wait(), e é suspensa – o evento começa no estado limpo, por isso a chamada wait cede de volta ao ciclo. Um segundo depois main chama evt.set() e a tarefa de espera é agendada para retomar. O await asyncio.sleep(0) final é apenas uma cedência para que o ciclo tenha oportunidade de correr a tarefa de espera antes de main retornar.

8.7.2. Os quatro métodos

  • set() – define o evento e agenda todas as coroutines atualmente bloqueadas em wait() para retomar. O evento permanece definido até ser limpo.

  • clear() – repõe o evento para o estado limpo. As coroutines que chamarem await wait() depois disso bloquearão novamente.

  • wait() – uma coroutine. Se o evento já estiver definido, retorna imediatamente. Caso contrário, bloqueia até algo o definir.

  • is_set() – devolve o estado atual sem bloquear. Útil quando uma coroutine quer verificar o evento sem esperar por ele.

8.7.3. Múltiplos aguardadores

Várias coroutines podem fazer await no mesmo evento. Quando algo chama set(), todas são agendadas para retomar. O evento é por defeito de um-para-muitos; não é necessário ter vários eventos para encaminhar o sinal para vários aguardadores.

8.7.4. Reutilização de um evento

Um padrão comum é usar um evento repetidamente – uma coroutine que corre uma vez por sinal – limpando-o após cada passagem:

async def worker(go):
    while True:
        await go.wait()
        do_one_unit_of_work()
        go.clear()

O produtor define o evento sempre que há trabalho; o trabalhador limpa-o depois de ter recebido o sinal. Se o produtor puder definir o evento novamente antes de o trabalhador ter acordado, o trabalhador pode querer tratar esse caso (while go.is_set(): ...) antes de limpar.

8.7.5. Contextos seguros

Event não é seguro para chamar set() de dentro de um handler de interrupção. O mecanismo que usa para agendar os aguardadores assume que está a correr dentro do contexto do próprio ciclo de eventos, o que as interrupções não satisfazem. Para acordar uma tarefa asyncio a partir de um handler de interrupção, ThreadSafeFlag (abordado em breve) é a primitiva correta.