'Can I await the same Task multiple times in Python?

I need to do a lot of work, but luckily it's easy to decouple into different tasks for asynchronous execution. Some of those depend on each other, and it's perfectly clear to me how on task can await multiple others to get their results. However, I don't know how I can have multiple different tasks await the same coroutine, and both get the result. The Documentation also doesn't mention this case as far as I can find. Consider the following minimal example:

from asyncio import create_task, gather

async def TaskA():
    ...  # This is clear
    return result

async def TaskB(task_a):
    task_a_result = await task_a
    ...  # So is this
    return result

async def TaskC(task_a):
    task_a_result = await task_a
    ...  # But can I even do this? 
    return result

async def main():
    task_a = create_task(TaskA())
    task_b = create_task(TaskB(task_a))
    task_c = create_task(TaskC(task_a))
    gather(task_b, task_c)  # Can I include task_a here to signal the intent of "wait for all tasks"?

For the actual script, all tasks do some database operations, some of which involve foreign keys, and therefore depend on other tables already being filled. Some depend on the same table. I definitely need:

  1. All tasks run once, and only once
  2. Some tasks are dependent on others being done before starting.

In brief, the question is, does this work? Can I await the same instantiated coroutine multiple times, and get the result every time? Or do I need to put awaits in main(), and pass the result? (which is the current setup, and I don't like it.)



Solution 1:[1]

You can await the same task multiple times:

from asyncio import create_task, gather, run


async def coro_a():
    print("executing coro a")
    return 'a'


async def coro_b(task_a):
    task_a_result = await task_a
    print("from coro_b: ", task_a_result)
    return 'b'


async def coro_c(task_a):
    task_a_result = await task_a
    print("from coro_a: ", task_a_result)
    return 'c'


async def main():
    task_a = create_task(coro_a())
    print(await gather(coro_b(task_a), coro_c(task_a)))


if __name__ == "__main__":
    run(main())

Will output:

executing coro a
from coro_b:  a
from coro_a:  a
['b', 'c']

What you can not do is to await the same coroutine multiples times:

...

async def main():
    task_a = coro_a()
    print(await gather(coro_b(task_a), coro_c(task_a)))
...

Will raise RuntimeError: cannot reuse already awaited coroutine.

As long as you schedule your coroutine coro_a using create_task your code will work.

Solution 2:[2]

Practically you can await the same task multiple times but it will be executed only one time.

What does it mean?

A task has some states such as pending, canceled, done, etc. Once it is awaited its state becomes a done and if you await it again it will not execute that second time as the task is already done.

Then why do we need the tasks?

Tasks are used to run coroutines concurrently. When you create a task with create_task it adds the coroutine to the event loop. And the gather just takes multiple coroutines in arguments and by creating tasks for them adds to the event loop running them concurrently.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Guillermo Soto Gómez
Solution 2 Artyom Vancyan