8.7. Eventos

asyncio.Event es la primitiva de señalización más simple que ofrece el módulo. Una corrutina establece un evento cuando algo ha ocurrido; cualquier número de otras corrutinas esperan a que el evento esté establecido antes de continuar. No hay carga útil – un evento es un booleano que se activa y permanece activado hasta que algo lo borra.

8.7.1. El patrón 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())

La tarea en espera se ejecuta, llega a await evt.wait() y queda suspendida – el evento empieza en estado borrado, por lo que la llamada a wait cede el control al bucle. Un segundo después, main llama a evt.set() y la tarea en espera queda planificada para reanudarse. El await asyncio.sleep(0) final es solo un yield para que el bucle tenga la oportunidad de ejecutar la tarea en espera antes de que main retorne.

8.7.2. Los cuatro métodos

  • set() – activa el evento y planifica la reanudación de toda corrutina actualmente bloqueada en wait(). El evento permanece establecido hasta que se borra.

  • clear() – vuelve a poner el evento en estado borrado. Las corrutinas que hagan await wait() después se bloquearán de nuevo.

  • wait() – una corrutina. Si el evento ya está establecido, retorna de inmediato. En caso contrario, se bloquea hasta que algo lo establezca.

  • is_set() – devuelve el estado actual sin bloquear. Útil cuando una corrutina quiere comprobar el evento sin esperar por él.

8.7.3. Múltiples esperadores

Varias corrutinas pueden hacer await sobre el mismo evento. Cuando algo llama a set(), todas quedan planificadas para reanudarse. El evento es de uno a muchos de forma predeterminada; no hace falta tener varios eventos para repartir hacia varios esperadores.

8.7.4. Reutilizar un evento

Un patrón común es usar un evento repetidamente – una corrutina que se ejecuta una vez por cada señal – borrándolo después de cada pasada:

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

El productor establece el evento cada vez que hay trabajo; el trabajador lo borra una vez que ha recogido la señal. Si el productor pudiera establecer el evento de nuevo antes de que el trabajador haya despertado, el trabajador puede que quiera manejar ese caso (while go.is_set(): ...) antes de borrarlo.

8.7.5. Contextos seguros

No es seguro llamar a set() sobre Event desde dentro de un manejador de interrupciones. El mecanismo que usa para planificar a los esperadores asume que se está ejecutando dentro del propio contexto del bucle de eventos, lo cual no ocurre con las interrupciones. Para despertar una tarea de asyncio desde un manejador de interrupciones, ThreadSafeFlag (que se cubre en breve) es la primitiva adecuada.