Search code examples
pythonpython-3.xqueuepython-asyncioproducer-consumer

Why is asyncio queue await get() blocking?


Why is await queue.get() blocking?

import asyncio

async def producer(queue, item):
    await queue.put(item)

async def consumer(queue):
    val = await queue.get()
    print("val = %d" % val)

async def main():
    queue = asyncio.Queue()
    await consumer(queue)
    await producer(queue, 1)


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

If I call the producer() before consumer(), it works fine That is to say, the following works fine.

async def main():
    queue = asyncio.Queue()
    await producer(queue, 1)
    await consumer(queue)

Why isn't await queue.get() yielding control back to the event loop so that the producer coroutine can run which will populate the queue so that queue.get() can return.


Solution

  • You need to start the consumer and the producer in parallel, e.g. defining main like this:

    async def main():
        queue = asyncio.Queue()
        await asyncio.gather(consumer(queue), producer(queue, 1))
    

    If for some reason you can't use gather, then you can do (the equivalent of) this:

    async def main():
        queue = asyncio.Queue()
        asyncio.create_task(consumer(queue))
        asyncio.create_task(producer(queue, 1))
        await asyncio.sleep(100)  # what your program actually does
    

    Why isn't await queue.get() yielding control back to the event loop so that the producer coroutine can run which will populate the queue so that queue.get() can return.

    await queue.get() is yielding control back to the event loop. But await means wait, so when your main coroutine says await consumer(queue), that means "resume me once consumer(queue) has completed." Since consumer(queue) is itself waiting for someone to produce something, you have a classic case of deadlock.

    Reversing the order works only because your producer is one-shot, so it immediately returns to the caller. If your producer happened to await an external source (such as a socket), you would have a deadlock there as well. Starting them in parallel avoids the deadlock regardless of how producer and consumer are written.