【问题标题】:Python asyncio Await TasksPython asyncio 等待任务
【发布时间】:2019-04-11 09:33:07
【问题描述】:

需要:Python 3.7 或更高版本。

下面定义了两个函数main1main2。一个创建任务,然后在最后等待所有任务;另一个创建并一次等待。

main1 需要 2 秒,main2 需要 30 秒。为什么?

import asyncio

async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)

async def main1():
    tasks = []
    for _ in range(10):
        task1 = asyncio.create_task(say_after(1, 'hello'))
        task2 = asyncio.create_task(say_after(2, 'world'))
        tasks.append(task1)
        tasks.append(task2)
    for x in tasks:
        await x

async def main2():
    for _ in range(10):
        await asyncio.create_task(say_after(1, 'hello'))
        await asyncio.create_task(say_after(2, 'world'))

asyncio.run(main2())

编辑 1:

这是一个main3 版本,需要 20 秒。我想说整个事情只是出于直觉:(

async def main3():
    for _ in range(10):
        task1 = asyncio.create_task(say_after(1, 'hello'))
        task2 = asyncio.create_task(say_after(2, 'world'))
        await task1
        await task2

编辑 2:

(下面添加了更多示例代码)我已经阅读了@freakish 的详细答案,但我仍然停留在一点:所以只有连续的await 才能整体并行工作(main4) ?

既然create_task() 不需要时间(对吗?),为什么不在main5 中的两个await 都在后台运行,这样main5 将花费(task1,task2)的最大时间?

这是 await 机制的设计,还是只是 asyncio 的限制(在设计或实现中)?

以及在官方 Python 文档中定义的任何 await 详细行为?

# took 2 seconds
async def main4():
    task1 = asyncio.create_task(say_after(1, 'hello'))
    task2 = asyncio.create_task(say_after(2, 'world'))
    await task1
    await task2

# took 3 seconds
async def main5():
    task1 = asyncio.create_task(say_after(1, 'hello'))
    await task1
    task2 = asyncio.create_task(say_after(2, 'world'))
    await task2

【问题讨论】:

  • why not both two await in main5 run in background 他们有。在 task1 完成之前,它只是 task2 不存在。因为task2 = create_task(...) 之前有await task1await 指令(与 create_task 不同)阻止代码前进,直到任务完成。
  • @freakish 所以在 asyncio 中没有什么神器可以“预见”协程中的所有任务并使它们协同工作?我能说 asyncio 不够聪明吗(即为什么要创建的 task2(在 main5 中)必须等待第一个 await 完成,无论如何都要等待)
  • Asyncio 不是预言机,你是对的。但不够聪明?锤子不聪明吗?它是一个工具,这取决于你如何使用它。正如您在我的回答中看到的,您可以创建许多 非等效 流。没有更好/更差的流程。它们中的每一个都有优点和缺点,可能有用,这取决于您要实现的目标。归根结底,只有程序员知道他想在哪里等待。没有编译器/解释器可以为您做出决定。

标签: python python-3.x python-asyncio


【解决方案1】:

因为main1同时创建所有个任务,然后在创建后等待所有个任务。一切都是并行发生的。所以总时间是所有时间中的最大值,即 2 秒。

虽然main2 仅在前一个任务完成 之后创建新任务。一切都是按顺序发生的。所以总时间是所有时间的总和,(从代码来看)应该是 30 秒。

编辑:说你有 3 个任务:task1, task2, task3。如果你这样做了

  1. 创建任务1
  2. 等待任务1
  3. 创建任务2
  4. 等待任务2
  5. 创建任务3
  6. 等待任务3

那么总执行时间显然是task1.time + task2.time + task3.time,因为没有后台处理。流程是顺序的。现在让我们说你做到了

  1. 创建任务1
  2. 创建任务2
  3. 创建任务3
  4. 等待任务1
  5. 等待任务2
  6. 等待任务3

现在task1, task2, task3 在后台运行。所以它需要T1 = task1.time 来处理 4。但是在 pt 5 需要T2 = max(task2.time - T1, 0) 来处理它,因为它已经在后台工作了T1 时间。在 pt 6 需要 T3 = max(task3.time - T2 - T1, 0) 来处理它,因为它已经在后台工作了 T1+T2 时间。现在需要一些数学来计算T1+T2+T3=max(task1.time, task2.time, task3.time) 的总和。

但直觉是这样的:如果taskX 是最长的一个并且它完成了,那么由于并行处理,其他一切都完成了。所以await 立即返回,使总处理时间成为所有时间的最大值。

旁注:有一些细微差别:这仅在您实际执行可并行化的事情(例如asyncio.sleep())时才有效。如果这些任务是同步的(比如一些 cpu 计算),那么这两种情况都会给 30 秒。

Edit2:所以你的main3 有一点不同的流程。它允许两个任务并行运行。但仅此而已:

  1. 创建任务1
  2. 创建任务2
  3. 等待任务1
  4. 等待任务2
  5. 创建任务3
  6. 创建任务4
  7. 等待任务3
  8. 等待任务4

所以这次task1task2 并行发生。但只有在它们完成之后,task3task4 才能运行。在平行下。因此,对于每个组,总时间最长,但您必须对不同的组求和。 IE。总执行时间为max(task1.time, task2.time)+max(task3.time, task4.time),在您的情况下是

max(1,2) + ... + max(1,2) [10 times] = 20

【讨论】:

  • 我希望得到这样的答案。但结果并不直观。您能在此处提供更多详细信息吗?
  • @mitnk 我还为main3添加了评论。
  • 非常感谢,我添加了一个有问题的“编辑 2”部分 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-09
  • 1970-01-01
  • 2016-06-25
  • 2021-05-04
  • 2021-12-29
相关资源
最近更新 更多