为什么我需要将一个 python 协程包装成一个 task/when 才能使用任务或协程?

Why do I need to wrap a python coroutine into a task/when to use a task or a coroutine?

在python中,有3 main types awaitable objects:协程、任务和Futures。

我可以await一个协程,还有一个tasks

等待协程

import asyncio

async def nested():
    return 42

async def main():
    print(await nested())  # will print "42".

asyncio.run(main())

等待任务

import asyncio

async def nested():
    return 42

async def main():
    task = asyncio.create_task(nested())
    await task

asyncio.run(main())

首先将协程包装在任务中的价值是什么?看起来他们做同样的事情。

我什么时候需要使用任务还是协程?

创建 Task 将传递的协程安排在事件循环中 运行。您可以使用 Task 取消底层协程。

在这种情况下,没有真正的区别:通过等待协程,它将作为任务的一部分进行调度。然而,这意味着它是由其父级驱动的。

通过将协程包装在任务中,它可以在事件循环中独立调度,这意味着它不再由包含任务驱动(它有自己的生命周期)并且可以与它进行更丰富的交互(例如取消或向其添加回调)。

认真思考“函数”与“线程”。协程只是一个可以暂停的函数(如果它等待东西),但它仍然只存在于其调用者的词汇和动态上下文中。任务从该上下文中释放出来,它使包装的协程过自己的生活,就像线程使包装的函数(目标)过自己的生活一样。

Coroutine 只是 运行 在当前 awaitable 上下文中的一个函数。它可以代表调用者(调用await的人)让出执行事件循环。想想一个允许暂停它的线程的函数。您可以从一个协程调用另一个协程,但它们仍然共享同一个线程。

另一方面,

Task 立即将单独的 job 发布到事件循环。任务本身是该作业的句柄。您可以 await 一个任务,但它可以 运行 在“并行”中对自己很好——在单线程上下文中,这意味着该任务可以 运行 而其他作业正在屈服(例如等待I/O)。任务甚至可能在您调用 await.

之前完成

没有任务的例子:

job_1 = sleep(5)
job_2 = sleep(2)

# will sleep for 5 seconds
await job_1

# will sleep for another 2 seconds
await job_2

任务示例:

job_1 = sleep(5)
job_2 = asyncio.create_task(sleep(2))

# will sleep for 5 seconds
await job_1

# by this time, job_2 is complete
# because previous job has yielded at some point, allowing other jobs to run
# thus await takes no time
await job_2