https://trio.readthedocs.io/
It's a much simpler model that's just as powerful, and makes it easy to get things right. It eliminates the concepts of futures, promises, and awaitables. It has only one way to wait for a task: await it.
For a theoretical explanation of it's "structured concurrency" model see the now-famous essay https://vorpus.org/blog/notes-on-structured-concurrency-or-g...
I've been stung, time and again by asyncio.create_task() swallowing exceptions[1], and functions not telling callers about background tasks[2].
For others looking to NOT rewrite all their code using a 3rd party event loop, here's a simple reproduction of nursery's behaviour in raw asyncio -
Hopefully, this will help lift the curse of asyncio :)
[EDIT] A library is also available - https://github.com/Tygs/ayo (with amazing operator overload)
async def main(): async with Nursery() as ny: ny.start_soon(a_1(), a_2(), ..., a_n()) ny.start_soon(b_1(), b_2(), ..., b_n()) . . . ny.start_soon(x_1(), x_2(), ..., x_n()) class Nursery: def __init__(self): self.tasks = set() def start_soon(self, *coros: typing.Coroutine): for coro in coros: self.tasks.add(asyncio.create_task(coro)) async def __aenter__(self): return self async def __aexit__(self, *args): try: while self.tasks: tasks = self.tasks self.tasks = set() done, pending = await asyncio.wait( tasks, return_when=asyncio.FIRST_COMPLETED ) try: for task in done: await task finally: self.tasks |= pending finally: for task in self.tasks: task.cancel()
[2] https://github.com/encode/starlette/issues/947
https://trio.readthedocs.io/
It's a much simpler model that's just as powerful, and makes it easy to get things right. It eliminates the concepts of futures, promises, and awaitables. It has only one way to wait for a task: await it.
For a theoretical explanation of it's "structured concurrency" model see the now-famous essay https://vorpus.org/blog/notes-on-structured-concurrency-or-g...