asyncio.create_task()是做什么的?

107

asyncio.create_task() 是什么?我查看了文档,但似乎无法理解它。让我困惑的一些代码是:

import asyncio

async def counter_loop(x, n):
    for i in range(1, n + 1):
        print(f"Counter {x}: {i}")
        await asyncio.sleep(0.5)
    return f"Finished {x} in {n}"

async def main():
    slow_task = asyncio.create_task(counter_loop("Slow", 4))
    fast_coro = counter_loop("Fast", 2)

    print("Awaiting Fast")
    fast_val = await fast_coro
    print("Finished Fast")

    print("Awaiting Slow")
    slow_val = await slow_task
    print("Finished Slow")

    print(f"{fast_val}, {slow_val}")

asyncio.run(main())

这将产生以下输出:

001 | Awaiting Fast
002 | Counter Fast: 1
003 | Counter Slow: 1
004 | Counter Fast: 2
005 | Counter Slow: 2
006 | Finished Fast
007 | Awaiting Slow
008 | Counter Slow: 3
009 | Counter Slow: 4
010 | Finished Slow
011 | Finished Fast in 2, Finished Slow in 4

我不太明白这是如何工作的。

  1. slow_task 没有被用在 asyncio.gather 方法中,为什么它能在 fast_coro 完成之前运行?
  2. 我们为什么需要 await slow_task
  3. 为什么 Awaiting Slow 在协程启动后才打印出来?
  4. 什么是一个 task?我知道 gather 所做的是调度一个 task,而 create_task 则被认为是创建一个 task

详细的答案将不胜感激。谢谢!

另外值得一提的是,我对 Futures 的了解非常有限。


正如日志所示,任务立即执行。 - Pynchia
1个回答

125

async def main():

# starting the slow task normally
result = await slow_task()

# starting the slow task after fast_coro completes
await fast_coro()
result = await slow_task()

# starting the slow task in the background while fast_coro runs
slow_task_handle = asyncio.create_task(slow_task())
await fast_coro()
result = await slow_task_handle

那么slow_task将会在fast_coro结束后才开始运行。

    slow_coro = counter_loop("Slow", 4)
    fast_coro = counter_loop("Fast", 2)
    fast_val = await fast_coro

确实,因为没有人将slow_coro提交到事件循环中,所以它不会运行。但是create_task正是这样做的:将其提交到事件循环中与其他任务并发执行,切换点是任何await

因为它从未在asyncio.gather方法中使用过吗?

asyncio.gather并不是在asyncio中实现并发的唯一方法。它只是一个实用函数,使等待多个协程全部完成变得更容易,并同时将它们提交到事件循环中。 create_task仅进行提交,它可能应该被称为start_coroutine或类似名称。

为什么我们必须等待slow_task

我们不一定要等待,只是为了让两个协程都干净地完成。代码也可以等待asyncio.sleep()之类的东西。在仍有一些任务未完成的情况下立即从main()(和事件循环)返回也可以正常工作,但它会打印一个警告消息,表明可能存在错误。在停止事件循环之前等待(或取消)任务只是更清晰的做法。

任务真正是什么?

它是一个asyncio构造,用于跟踪协程在具体事件循环中的执行。当您调用create_task时,您提交了一个协程以供执行,并收回一个句柄。当您实际需要结果时可以等待此句柄,或者如果你不关心结果,可以从不等待它。这个句柄就是任务,它继承自Future,使其可等待并提供低级别的基于回调的接口,例如add_done_callback


5
asyncio.gather(*list_of_tasks)会等待任务完成,并返回它们各自协程的结果。create_task 返回一个任务,你可以使用await获取正在运行的任务的结果。如果任务已经完成,不用担心,它仍然会保留结果。是的,gather在需要时会方便地调用create_task,因为它支持除了协程之外的可等待对象。 - user4815162342
3
@BeastCoder 大致上是这样的。类似于 tasks = [asyncio.create_task(c) for c in list_of_coros]; results = [await t for t in tasks] 这样的代码可以作为 gather 的第一个近似实现。然而,实际的实现要复杂得多,因为 gather 非常注意如何传播异常、如何响应取消以及其他各种问题。 - user4815162342
3
@ArnabMukherjee,这里没有真正的“背景”,当你等待某个东西时,它们就会运行,并且它们准备好了运行。这些评论应该被发布为一个单独的问题;参见这个 - user4815162342
5
如果您指的是问题中给出的代码,那么就存在这种情况。通常情况下,asyncio不能保证可运行任务的执行顺序,但这里的代码等待的是一个协程而不是一个任务。在直接await的协程中,代码会立即执行(不会让出事件循环),直到第一次挂起为止。由于在await fast_coro和第一个print之间没有挂起,它总是会先于来自其他任务的任何输出。 - user4815162342
2
@aleks224 立即执行等待中的协程直到其暂停是有保证的,尽管您可能会发现在文档中难以找到明确说明。基本上,这源于awaityield from的规范和实现方面是一致的,并且这就是yield from必须遵循以满足PEP 380 的_重构原则_的方式。 - user4815162342
显示剩余10条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接