Margaret found Timothy in the library’s coordination room, staring at a wall covered in task dependency charts. ‘The Event pattern works beautifully for simple signals,’ he said, ‘but I’ve hit something more complex. I need tasks to wait for specific conditions to become true, then wake up and check again. Events are too blunt - they’re either set or not set. I need something more nuanced.’
‘Ah,’ Margaret smiled, ‘you need a Condition. Think of it as a smart waiting room where tasks can sleep until they’re told that something interesting has changed, then wake up to check if the change matters to them specifically.’
The Problem: When Events Aren’t Enough
Timothy showed Margaret his code for managing a shared resource with capacity limits:
import asyncio
# Attempt 1: Usi...
Margaret found Timothy in the library’s coordination room, staring at a wall covered in task dependency charts. ‘The Event pattern works beautifully for simple signals,’ he said, ‘but I’ve hit something more complex. I need tasks to wait for specific conditions to become true, then wake up and check again. Events are too blunt - they’re either set or not set. I need something more nuanced.’
‘Ah,’ Margaret smiled, ‘you need a Condition. Think of it as a smart waiting room where tasks can sleep until they’re told that something interesting has changed, then wake up to check if the change matters to them specifically.’
The Problem: When Events Aren’t Enough
Timothy showed Margaret his code for managing a shared resource with capacity limits:
import asyncio
# Attempt 1: Using Event (doesn't quite work)
class SharedResource:
def __init__(self, capacity):
self.capacity = capacity
self.in_use = 0
self.available_event = asyncio.Event()
self.available_event.set() # Initially available
async def acquire(self):
while True:
await self.available_event.wait()
if self.in_use < self.capacity:
self.in_use += 1
if self.in_use >= self.capacity:
self.available_event.clear()
return
# Race condition: another task might have grabbed it ___eWEyOS5hMEFUaTZLMnVpQ0JUNlFQbVlzWUVrdlZLeGYwTnhCR2ljdC1hRjZ0WTZ1UlFTd3c4NHh4ckJMQjVKRE4zWDZROTg0eVFQYWJMUzR4NnZiRG45R09WWDFBUmhBZjhnUnpUQWlmSzJiSXItYnhGbDJaNGNuaEhNbXBDRmI3U2VQTXhjMnU0LUJGTG9pR09yQnFNX2RMcUowYmEtWTcydVA1cDFfTFFSaUhTM1FxNm83RFliTmtsWjFkdFNTM2ZMeWM2SVZmZXJ4YTRhQ2dZS0FVY1NBUkVTRlFIR1gyTWlzSk9vQTRZeTdZUzFQWlM4eVNRR3d3MDIwNg==___
# We need to wait again, but the event is still set!
async def release(self):
self.in_use -= 1
self.available_event.set()
async def demonstrate_event_problem():
resource = SharedResource(capacity=2)
async def worker(worker_id):
print(f'Worker {worker_id} trying to acquire')
await resource.acquire()
print(f'Worker {worker_id} acquired resource')
await asyncio.sleep(2)
await resource.release()
print(f'Worker {worker_id} released resource')
# Create 5 workers competing for 2 slots
await asyncio.gather(*[worker(i) for i in range(5)])
# asyncio.run(demonstrate_event_problem())