8.7. Eventos

asyncio.Event é a primitiva de sinalização mais simples que o módulo oferece. Uma corrotina define (sets) um evento quando algo aconteceu; qualquer número de outras corrotinas esperam que o evento seja definido antes de continuar. Não há carga útil – um evento é um booleano que se ativa e permanece ativo até que algo o limpe.

8.7.1. O formato básico

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 que espera (waiter) executa, atinge await evt.wait() e é suspensa – o evento começa no estado limpo, então a chamada wait cede o controle de volta ao loop. Um segundo depois, main chama evt.set() e o waiter é agendado para retomar. O await asyncio.sleep(0) ao final é apenas um yield para que o loop tenha a chance de executar o waiter antes que main retorne.

8.7.2. Os quatro métodos

  • set() – ativa o evento e agenda toda corrotina atualmente bloqueada em wait() para retomar. O evento permanece ativo até ser limpo.

  • clear() – retorna o evento ao estado limpo. Corrotinas que fizerem await wait() depois disso serão bloqueadas novamente.

  • wait() – uma corrotina. Se o evento já estiver ativo, retorna imediatamente. Caso contrário, bloqueia até que algo o ative.

  • is_set() – retorna o estado atual sem bloquear. Útil quando uma corrotina deseja verificar o evento sem esperar por ele.

8.7.3. Múltiplos waiters

Várias corrotinas podem fazer await no mesmo evento. Quando algo chama set(), todas são agendadas para retomar. O evento é um-para-muitos por padrão; não há necessidade de vários eventos para distribuir para vários waiters.

8.7.4. Reutilizando um evento

Um padrão comum é usar um evento repetidamente – uma corrotina que executa 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 o limpa assim que captou o sinal. Se o produtor puder definir o evento novamente antes que o trabalhador tenha acordado, o trabalhador pode querer tratar esse caso (while go.is_set(): ...) antes de limpar.

8.7.5. Contextos seguros

Não é seguro chamar set() em Event de dentro de um manipulador de interrupção. O mecanismo que ele usa para agendar os waiters pressupõe que está executando dentro do próprio contexto do event loop, o que as interrupções não são. Para acordar uma tarefa asyncio a partir de um manipulador de interrupção, ThreadSafeFlag (coberto em breve) é a primitiva correta.