Skip to content

Async Queues (producer-consumer)

diagram an asyncio.Queue is how producers and consumers hand work over mermaid
The queue is what lets the two sides run at their own pace without either polling the other. A consumer awaiting get is suspended, costing nothing, until an item exists. A bounded queue also pushes back: once it is full, put suspends the producer, which is what stops a fast producer from exhausting memory.

asyncio.Queue enables:

  • safe communication between coroutines
  • backpressure (limit queue size)
async_queue.py
import asyncio
 
 
async def producer(q: asyncio.Queue):
    for i in range(10):
        await q.put(i)
        print("produced", i)
    await q.put(None)  # sentinel
 
 
async def consumer(q: asyncio.Queue):
    while True:
        item = await q.get()
        try:
            if item is None:
                break
            print("consumed", item)
            await asyncio.sleep(0.1)
        finally:
            q.task_done()
 
 
async def main():
    q = asyncio.Queue(maxsize=5)
 
    p = asyncio.create_task(producer(q))
    c = asyncio.create_task(consumer(q))
 
    await p
    await q.join()
    await c
 
 
asyncio.run(main())
  • Use a sentinel to stop consumers.
  • Use maxsize for backpressure.
sketch An asyncio.Queue suspends instead of blocking p5.js
Same producer-consumer shape as the threading version, but nothing blocks a thread. A consumer awaiting get is suspended and the loop runs other tasks; a full bounded queue suspends the producer instead. That back-pressure is the whole reason to give the queue a maxsize -- without it a fast producer will happily exhaust memory.
pch.quizTag pch.quizDefaultTitle
  1. A consumer awaits `q.get()` on an empty `asyncio.Queue`. What is it doing?

    pch.quizShowAnswer

    B — Suspended — the loop runs other tasks meanwhile — Awaiting suspends the task without blocking the thread, which is why thousands of waiting tasks are cheap while thousands of waiting threads are not.

  2. What does giving the queue a `maxsize` buy you?

    pch.quizShowAnswer

    B — Back-pressure — a full queue suspends the producer — Unbounded, a producer faster than its consumer grows the queue until memory runs out — and that failure looks like a slow leak rather than a queue problem.

  3. What does `await q.join()` wait for?

    pch.quizShowAnswer

    B — Every item that was put to have a matching `task_done()` — It tracks completion of the WORK, not merely that the queue has drained. An item removed but not yet processed still counts as outstanding.

  4. Why use `asyncio.Queue` rather than `queue.Queue` inside an event loop?

    pch.quizShowAnswer

    B — `queue.Queue` blocks the thread, which stalls every task including the consumer — Same reasoning as `threading.Semaphore` in async code: blocking the single thread stops the tasks that would have made progress possible.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading