quattro.gather

quattro comes with an independent, simple implementation of asyncio.gather() based on Task Groups. The quattro version supports limiting concurrency, is safer, and uses a task group under the hood to not leak tasks in cases of errors in child tasks.

When and where to use

Since it’s almost a drop-in replacement for asyncio.gather(), use everywhere for more predicable handling of failed tasks.

from quattro import gather

async def my_handler():
    res_1, res_2 = await gather(long_query_1(), long_query_2())

The return_exceptions argument can be used to make gather() catch and return exceptions as responses instead of letting them bubble out.

from quattro import gather

async def my_handler():
    res_1, res_2 = await gather(
        long_query_1(),
        long_query_2(),
        return_exceptions=True,
    )

    # res_1 and res_2 may be instances of exceptions.

The concurrency_limit argument can be used to limit how many child tasks execute in parallel.

from quattro import gather

async def my_handler():
    res = await gather(
        *(fetch_page(url) for url in urls),
        concurrency_limit=10,
    )

The differences to asyncio.gather() are:

  • If a child task fails other unfinished tasks will be cancelled, just like in a TaskGroup.

  • quattro.gather() only accepts coroutines and not futures and generators, just like a TaskGroup.

  • When return_exceptions is false (the default), an exception in a child task will cause an ExceptionGroup to bubble out of the top-level gather() call, just like in a TaskGroup.

  • Results are returned as a tuple, not a list.