How asyncio.Event Coordinates Coroutines in Python

Python's asyncio.Event provides a synchronization primitive that allows one or more coroutines to wait for a specific signal or state change before resuming execution. It functions around an internal boolean flag that defaults to False. Coroutines pause their execution using the wait() method, and when a signalling coroutine invokes set(), all awaiting coroutines are immediately scheduled to continue. This mechanism eliminates the need for busy-waiting loops, providing an efficient, non-blocking pattern for one-to-many coroutine notification within an event loop.

Core Methods and State Management

An asyncio.Event manages state through four primary methods:

How the Signalling Mechanism Works Under the Hood

When a coroutine calls await event.wait() while the event is unset, the Event instance creates an internal asyncio.Future object. The calling coroutine registers itself on this future and yields control back to the event loop.

Multiple coroutines can await the same Event instance simultaneously. The event object maintains an internal collection of these pending futures.

When another coroutine executes event.set():

  1. The event updates its internal state flag to True.
  2. It iterates through all registered futures in its collection and sets their results.
  3. The event loop marks the paused coroutines as ready to run.
  4. On subsequent iterations of the event loop, all awakened coroutines resume execution concurrently.

Because waking up the coroutines does not automatically reset the flag, any subsequent coroutine that calls await event.wait() will continue immediately without pausing, unless event.clear() is called explicitly.

Code Example: One-to-Many Notification

The following example demonstrates a single worker initiating an initialization process, while multiple consumer coroutines pause until initialization completes:

import asyncio

async def worker(worker_id: int, ready_event: asyncio.Event):
    print(f"Worker {worker_id} is waiting for the ready signal...")
    await ready_event.wait()
    print(f"Worker {worker_id} received signal and started processing.")

async def initializer(ready_event: asyncio.Event):
    print("Initializer: Setting up resources...")
    await asyncio.sleep(2)  # Simulate initialization delay
    print("Initializer: Setup complete. Signalling workers.")
    ready_event.set()

async def main():
    ready_event = asyncio.Event()

    # Launch multiple workers waiting for the same event
    workers = [asyncio.create_task(worker(i, ready_event)) for i in range(1, 4)]
    
    # Launch initializer
    init_task = asyncio.create_task(initializer(ready_event))

    await asyncio.gather(init_task, *workers)

asyncio.run(main())

Key Considerations