0%

执行可等待对象:asyncio.ensure_future()

asyncio.ensure_future() 用于调度协程(coroutine)或其他可等待对象(awaitable)的执行。它的核心功能是确保一个可等待对象最终被包装成一个 asyncio.Task 对象,并安排它在事件循环中运行。

asyncio.ensure_future 的主要作用

  1. 调度执行:当你有一个协程函数,但不想立即 await 它(因为 await 会阻塞当前任务直到该协程完成),你可以使用 asyncio.ensure_future() 来把它提交给事件循环,让它在后台并发执行。

  2. 统一对象类型:这个函数非常灵活,它可以接受多种类型的输入:

    • 如果是协程(coroutine):它会调用 loop.create_task() 将协程包装成一个 Task 对象。 这个 Task 会被安排在事件循环中执行。
    • 如果是 FutureTask 对象:它会直接返回该对象,不做任何改变。 因为 Task 本身就是 Future 的子类。
    • 如果是其他可等待对象(awaitable):它会将其包装在一个 Task 中,该任务会 await 这个对象。

    这个特性使得 ensure_future() 在编写库或API时特别有用,因为你无需关心传入的到底是一个协程还是一个已经存在的 Taskensure_future() 都能确保你得到一个可以操作的 Future 对象(比如用于取消操作)。

asyncio.ensure_future vs asyncio.create_task

在现代 Python (3.7+) 中,asyncio.create_task() 是创建和调度任务的首选方式。 了解它们之间的区别很重要:

  • asyncio.create_task(coro)

    • 目的明确:专门用于从一个协程创建并调度一个 Task。 它是更高级别的API,推荐在应用代码中使用。
    • 输入限制:它的参数只能是协程对象。
    • 可读性好:函数名清晰地表达了其意图——创建一个任务。
  • asyncio.ensure_future(awaitable)

    • 功能更广泛:如上所述,它可以接受协程、FutureTask 或其他可等待对象。
    • 向后兼容create_task() 是在 Python 3.7 中引入的。在之前的版本中,ensure_future() 是创建任务的主要方式。
    • 适用场景:当你需要编写一个能同时处理协程和 Future 对象的通用函数时,ensure_future() 更加合适。

总结:在 Python 3.7 及更高版本中,如果你明确知道要从一个协程创建一个任务,应该优先使用 asyncio.create_task()。 只有在需要兼容旧版本或处理不确定是协程还是 Future 的输入时,才需要使用 asyncio.ensure_future()

如何使用 asyncio.ensure_future

下面是一个代码示例,展示了如何使用 ensure_future() 来并发执行任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import asyncio
import time

async def my_coroutine(name, delay):
"""一个模拟耗时操作的协程"""
print(f"任务 '{name}' 开始执行,将休眠 {delay} 秒")
await asyncio.sleep(delay)
print(f"任务 '{name}' 执行完毕")
return f"结果来自 {name}"

async def main():
start_time = time.time()
print("主程序开始")

# 使用 ensure_future() 来调度两个协程并发执行
# 它们会被包装成 Task 并立即开始运行
task1 = asyncio.ensure_future(my_coroutine("任务A", 2))
task2 = asyncio.ensure_future(my_coroutine("任务B", 3))

# ensure_future 接收一个已存在的 Task 时,会直接返回它
task3 = asyncio.ensure_future(task2)
print(f"task2 和 task3 是同一个对象吗? {task2 is task3}")

# 在这里可以做其他事情,而 task1 和 task2 正在后台运行
print("任务已调度,主程序可以继续执行其他操作")
await asyncio.sleep(1)
print("主程序等待 1 秒后继续...")

# 等待两个任务完成并获取结果
# 注意:可以直接 await task 对象
result1 = await task1
result2 = await task2

end_time = time.time()
print(f"获取到的结果: '{result1}', '{result2}'")
print(f"主程序结束,总耗时: {end_time - start_time:.2f} 秒")

if __name__ == "__main__":
asyncio.run(main())

运行结果分析:

  1. main 函数开始执行,task1task2 通过 asyncio.ensure_future() 被调度。它们会立即开始并发运行,而不是一个接一个。
  2. 程序打印 “任务已调度…”,并等待1秒。在此期间,task1task2 都在后台执行它们的 asyncio.sleep()
  3. await task1await task2 会等待各自的任务完成。由于它们是并发执行的,总耗时取决于最长的那个任务(任务B,3秒),而不是两个任务时间的总和(2 + 3 = 5秒)。
  4. 最终的总耗时约等于3秒,证明了并发执行的效率。
  5. 示例也验证了当 ensure_future 的参数已经是 Task 时,它会返回完全相同的对象。