我可以在 Python 中多次等待同一个任务吗?

Can I await the same Task multiple times in Python?

我需要做很多工作,但幸运的是,很容易解耦为不同的任务以异步执行。其中一些相互依赖,我非常清楚 on task 如何 await 多个其他人来获得他们的结果。但是,我不知道如何让多个不同的任务等待同一个协程,并且都得到结果。据我所知,Documentation 也没有提到这种情况。 考虑以下最小示例:

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"?

对于实际脚本,所有任务都进行一些数据库操作,其中一些涉及外键,因此取决于其他 tables 已经被填充。有些依赖于相同的 table。我绝对需要:

  1. 所有任务运行一次,而且只有一次
  2. 一些任务取决于其他任务在开始之前完成。

简而言之,问题是,这行得通吗?我可以多次等待同一个实例化协程,并且每次都得到结果吗?还是我 需要 将等待放入 main() 并传递结果? (这是当前的设置,我不喜欢它。)

您可以等待相同的 task 多次:

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())

将输出:

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

你不能做的是等待相同的 coroutine 倍数:

...

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

将提高 RuntimeError: cannot reuse already awaited coroutine.

只要您使用 create_task 安排协程 coro_a,您的代码就可以工作。

实际上,您可以多次等待同一个任务,但它只会执行一次。

What does it mean?

任务有一些状态,例如待定、取消、完成等。一旦等待,它的状态就会变为完成,如果您再次等待它 它不会执行第二次,因为任务已经完成。

Then why do we need the tasks?

任务用于 运行 协程并发。当您使用 create_task 创建任务时,它会将协程添加到事件循环中。 gather 只是在参数中采用多个协同程序,并通过为它们创建任务来将它们并发添加到事件循环中 运行。