简述
如果您想要立即安排协程的执行,但不一定要等待它完成,而是先转而处理其他事情,那么使用create_task
是有意义的。
解释
正如评论中已经指出的那样,asyncio.gather
本身会将提供的可等待对象包装成任务,因此在您的简单示例中预先调用create_task
实际上是多余的。
从gather
文档中可以看到:
如果任何可等待对象[...]是协程,则会自动将其安排为任务。
话虽如此,但是你构建的这两个示例并不等价!
当你调用create_task
时,任务会立即在事件循环上安排执行。这意味着,如果在你为所有协程调用create_task
后发生上下文切换(就像在第一个示例中一样),任何数量的协程都可能立即开始执行,而无需显式地await
它们。
从create_task
文档中可以看到:(我强调)
将[...]协程包装成Task
并安排其执行。
相比之下,当你仅仅创建协程(就像在第二个示例中一样),它们将不会自行开始执行,除非你以某种方式安排它们的执行(例如通过简单地await
它们)。
如果您在创建协程和调用gather
之间添加任何await
(例如asyncio.sleep
),并添加一些有用的print
语句,您就可以看到它的实际效果:
from asyncio import create_task, gather, sleep, run
async def process(index: int):
await sleep(.5)
print('ok:', index)
async def main1():
tasks = [create_task(process(item)) for item in range(5)]
print("tasks scheduled")
await sleep(2)
print("now gathering tasks")
await gather(*tasks)
print("gathered in main1")
async def main2():
coroutines = [process(item) for item in range(5)]
print("coroutines created")
await sleep(2)
print("now gathering coroutines")
await gather(*coroutines)
print("gathered in main2")
run(main1())
run(main2())
输出:
tasks scheduled
ok: 0
ok: 1
ok: 2
ok: 3
ok: 4
now gathering tasks
gathered in main1
coroutines created
now gathering coroutines
ok: 0
ok: 1
ok: 2
ok: 3
ok: 4
gathered in main2
正如您所看到的,在main1
示例中,process
体在gather
调用之前执行,而在main2
示例中,它仅在之后执行。
因此,是否使用create_task
取决于情况。如果您只关心协程在代码的特定点并发执行和等待,那么调用create_task
是没有用的。如果您想要安排它们,然后转而做其他事情,而它们可能或可能不会在后台完成任务,那么使用create_task
是有意义的。
但是,需要记住的一件重要事情是,只有在某个时刻await
它们,您才能确保您安排的任务实际上完全执行。这就是为什么您仍然应该await gather
它们(或等效物)以最终等待它们完成的原因。
create_task
函数提交协程以在“后台”中运行,即与当前任务和所有其他任务并发运行,在等待点之间进行切换。它返回一个可等待的句柄,称为“任务”,您还可以使用它来取消协程的执行。 - Cowasyncio.gather
已经将协程包装在任务中,因此自己创建任务并不会改变任何事情。 - user2357112asyncio.gather
时,添加asyncio.create_task
是不必要的吗? - Vicasyncio.gather
:如果aws
中的任何可等待对象是协程,它会自动作为任务调度。(请查阅文档) - Timusgather
调用之前引入了额外的上下文切换。在这个给定的例子中,确实没有区别,但通常情况下这种差异可能是巨大的。请参见下面我的答案以获取详细信息。 - Daniil Fajnberg