Stream of jobs with a fixed worker pool. Decouple job arrival from processing speed.
await queue.put() block, naturally slowing the producer. No data loss, no lost events.
await queue.get(). Only one gets each job. Use queue.join() to know when all jobs are done.
import asyncio
NUM_WORKERS = 3
SENTINEL = None # signal to stop
async def process(job):
"""Simulate async work: DB query, API call, etc."""
await asyncio.sleep(0.5)
print(f" โ job {job['id']}")
async def worker(name, queue):
while True:
job = await queue.get() # โ blocks here if queue is empty
try:
if job is SENTINEL:
return # shutdown signal
print(f"{name} picked up job {job['id']}")
await process(job)
finally:
queue.task_done() # notify queue that this job is done
async def producer(queue):
for i in range(10):
await queue.put({"id": i})
await asyncio.sleep(0.1)
async def main():
queue = asyncio.Queue(maxsize=5) # bounded โ backpressure
async with asyncio.TaskGroup() as tg:
# Start workers
for n in range(NUM_WORKERS):
tg.create_task(worker(f"worker-{n}", queue))
# Produce jobs
await producer(queue)
# Wait for all jobs to finish
await queue.join()
# Tell workers to stop
for _ in range(NUM_WORKERS):
await queue.put(SENTINEL)
print("done!")
asyncio.run(main())
await queue.get()
Suspends the coroutine until an item is available. Safe for concurrent access.
await queue.put(item)
Adds an item. Blocks if queue is full (backpressure).
queue.task_done()
Decrements the internal counter. Call after finishing a job.
await queue.join()
Waits until all jobs are done (counter = 0).
# Webhook handler (FastAPI)
@app.post("/webhook")
async def handle_webhook(event: dict):
await webhook_queue.put(event) # โ enqueue and return immediately
return {"status": "queued"}
# Background worker
async def webhook_processor():
while True:
event = await webhook_queue.get()
try:
await process_stripe_webhook(event) # network I/O
finally:
webhook_queue.task_done()
# Start worker on app startup
# Creates a bounded queue that slows webhook submissions if workers fall behind
webhook_queue = asyncio.Queue(maxsize=1000)
Producer doesn't wait for worker; it enqueues and continues. Workers pull from queue at their own pace.
Bounded queue fills if workers slow down, which blocks producer. No data loss, handles bursts.
Add more workers to handle faster processing. Queue automatically distributes jobs fairly.
Use SENTINEL to shut down workers cleanly. Use join() to know when done.