Is there a Pythonic way to run async task in background similar to using a contextmanager?

Question:

Recently I wanted to run some asynchronous tasks in the background while running other tasks but I didn’t think the code was Pythonic enough:

task = asyncio.create_task(long_task())
await short_task()
await task

So I made it more Pythonic:

@asynccontextmanager
async def run_in_background(coro):
    task = asyncio.create_task(coro)
    yield task
    await task


async def main():
    async with run_in_background(long_task()):
        await short_task()

Does something like this already exist? If not is this considered more Pythonic or less Pythonic than the existing way?

Asked By: Tom Gringauz

||

Answers:

Does something like this already exist?

Not at the moment, but it’s a very useful idea. A more general version of the concept will be added to Python 3.8 a future Python version in the form of a TaskGroup class inspired by prior art in Curio and Trio.

I would suggest enhancing the implementation to use finally, providing a guarantee that the background task will be awaited even in case of exception; for example:

@asynccontextmanager
async def run_in_background(coro):
    task = asyncio.create_task(coro)
    try:
        yield task
    finally:
        await task

If not is this considered more Pythonic or less Pythonic than the existing way?

This part of the question is obviously opinion-based, but I would say a context manager is more Pythonic because it ensures that the background task is done and awaited by the time the block is left. It also ensures that the exceptions in the background task do not pass silently, which is a frequent source of bugs in asyncio code.

Answered By: user4815162342

trio provides a better way to do this with nursery objects:

async with trio.open_nursery() as nursery:
    nursery.start_soon(long_task)  # Task will run in background

    await short_task()
    # Wait for background tasks to finish
Answered By: Tom Gringauz