asyncio.ensure_future vs. BaseEventLoop.create_task vs. 简单协程?

asyncio.ensure_future vs. BaseEventLoop.create_task vs. simple coroutine?

我看过几个关于 asyncio 的基本 Python 3.5 教程,它们以不同的风格执行相同的操作。 在这段代码中:

import asyncio  

async def doit(i):
    print("Start %d" % i)
    await asyncio.sleep(3)
    print("End %d" % i)
    return i

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    #futures = [asyncio.ensure_future(doit(i), loop=loop) for i in range(10)]
    #futures = [loop.create_task(doit(i)) for i in range(10)]
    futures = [doit(i) for i in range(10)]
    result = loop.run_until_complete(asyncio.gather(*futures))
    print(result)

上面定义 futures 变量的所有三个变体都获得了相同的结果;我能看到的唯一区别是,对于第三个变体,执行是乱序的(在大多数情况下这无关紧要)。还有其他区别吗?是否存在我不能只使用最简单的变体(协程的简单列表)的情况?

create_task()

  • 接受协程,
  • returns 任务,
  • 它在循环上下文中被调用。

ensure_future()

  • 接受 Futures、协程、等待对象,
  • returns 任务(或未来,如果未来通过)。
  • 如果给定的 arg 是协程,它使用 create_task
  • 可以传递循环对象

如您所见,create_task 更具体。


async 函数没有 create_task 或 ensure_future

简单调用async函数returns协程

>>> async def doit(i):
...     await asyncio.sleep(3)
...     return i
>>> doit(4)   
<coroutine object doit at 0x7f91e8e80ba0>

并且由于引擎盖下的 gather 确保 (ensure_future) args 是期货,明确地 ensure_future 是多余的。

类似问题

实际信息:

从Python 3.7 asyncio.create_task(coro) 高级函数was added 开始。

您应该使用它而不是其他方式从 coroutimes 创建任务。但是,如果您需要从任意可等待对象创建任务,则应使用 asyncio.ensure_future(obj).


旧信息:

ensure_future 对比 create_task

ensure_future是创建Task from coroutine的方法。它根据参数以不同的方式创建任务(包括使用 create_task 协程和类似未来的对象)。

create_taskAbstractEventLoop的抽象方法。不同的事件循环可以以不同的方式实现这个功能。

您应该使用 ensure_future 创建任务。仅当您要实现自己的事件循环类型时才需要 create_task

更新:

@bj0 在这个话题上指向 Guido's answer

The point of ensure_future() is if you have something that could either be a coroutine or a Future (the latter includes a Task because that's a subclass of Future), and you want to be able to call a method on it that is only defined on Future (probably about the only useful example being cancel()). When it is already a Future (or Task) this does nothing; when it is a coroutine it wraps it in a Task.

If you know that you have a coroutine and you want it to be scheduled, the correct API to use is create_task(). The only time when you should be calling ensure_future() is when you are providing an API (like most of asyncio's own APIs) that accepts either a coroutine or a Future and you need to do something to it that requires you to have a Future.

及以后:

In the end I still believe that ensure_future() is an appropriately obscure name for a rarely-needed piece of functionality. When creating a task from a coroutine you should use the appropriately-named loop.create_task(). Maybe there should be an alias for that asyncio.create_task()?

这让我很惊讶。我一直使用 ensure_future 的主要动机是,与循环成员 create_task 相比,它是更高级别的函数(讨论 contains 一些想法,例如添加 asyncio.spawnasyncio.create_task ).

我还可以指出,在我看来,使用可以处理任何 Awaitable 而不是仅协程的通用函数非常方便。

不过Guido的回答很明确:"When creating a task from a coroutine you should use the appropriately-named loop.create_task()"

何时应将协程包装在任务中?

在任务中包装协程 - 是启动此协程的一种方式 "in background"。示例如下:

import asyncio


async def msg(text):
    await asyncio.sleep(0.1)
    print(text)


async def long_operation():
    print('long_operation started')
    await asyncio.sleep(3)
    print('long_operation finished')


async def main():
    await msg('first')

    # Now you want to start long_operation, but you don't want to wait it finised:
    # long_operation should be started, but second msg should be printed immediately.
    # Create task to do so:
    task = asyncio.ensure_future(long_operation())

    await msg('second')

    # Now, when you want, you can await task finised:
    await task


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

输出:

first
long_operation started
second
long_operation finished

您可以将 asyncio.ensure_future(long_operation()) 替换为 await long_operation() 以感受差异。

对于您的示例,所有三种类型都是异步执行的。唯一的区别是,在第三个示例中,您预先生成了所有 10 个协程,并一起提交给循环。所以只有最后一个随机给出输出。

Note: Only valid for Python 3.7 (for Python 3.5 refer to the ).

来自官方文档:

asyncio.create_task (added in Python 3.7) is the preferable way for spawning new tasks instead of ensure_future().


详情:

所以现在,在 Python 3.7 之后,有 2 个顶级包装函数(相似但不同):

好吧,最终这两个包装器函数都会帮助您调用 BaseEventLoop.create_task. The only difference is ensure_future accept any awaitable 对象并帮助您将其转换为 Future。您还可以在 ensure_future 中提供自己的 event_loop 参数。根据您是否需要这些功能,您可以简单地选择要使用的包装器。