it-swarm.dev

asyncio.ensure_future与BaseEventLoop.create_task对比简单的协同程序?

我已经看过几个关于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 __== '__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变量的所有三个变体都实现了相同的结果;我能看到的唯一区别是,对于第三个变体,执行是乱序的(在大多数情况下这应该无关紧要)。还有其他区别吗?有些情况下我不能只使用最简单的变体(协程的简单列表)吗?

73
crusaderky

实际信息:

从Python 3.7 asyncio.create_task(coro)高级函数 开始添加 为此目的。

您应该使用它来代替从coroutimes创建任务的其他方法。但是,如果您需要从任意等待创建任务,您应该使用asyncio.ensure_future(obj)


旧信息:

ensure_future vs create_task

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

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

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

更新:

@ bj0指出 Guido的回答 关于这个话题:

ensure_future()的要点是,如果你有一些可以是协程或Future(后者包含Task,因为它是Future的子类),并且你希望能够在其上调用仅在Future上定义的方法。 (可能是唯一有用的例子是cancel())。当它已经是Future(或Task)时,它什么都不做;当它是一个协程时它包装它在Task中。

如果你知道你有一个协同程序并且你想要它被安排,那么使用正确的API就是create_task()。你应该调用ensure_future()的唯一一次是你提供一个API(比如大多数asyncio自己的API),它接受一个协同程序或一个Future,你需要对它做一些需要你有一个Future的东西。

然后:

最后,我仍然认为ensure_future()是一个非常需要的功能的名称。从协程创建任务时,您应该使用适当命名的loop.create_task()。也许应该有asyncio.create_task()的别名?

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

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

但是,Guido的答案很明确: “当从协程创建任务时,你应该使用适当命名的loop.create_task()

协同程序应该包装在任务中吗?

在任务中包装协程 - 是一种在后台启动此协程的方法。这是一个例子:

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 __== "__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()来感受差异。

88
Mikhail Gerasimov

create_task()

  • 接受协同程序,
  • 返回任务,
  • 它在循环的上下文中调用。

ensure_future()

  • 接受期货,协同程序,等待物品,
  • 返回Task(如果Future传递则返回Future)。
  • 如果给定的arg是一个协程,它使用create_task
  • 循环对象可以传递。

如您所见,create_task更具体。


没有create_task或ensure_future的async函数

简单地调用async函数返回协同程序

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

并且由于引擎盖下的 gather 确保(ensure_future)args是期货,所以显式ensure_future是多余的。

类似的问题 loop.create_task,asyncio.async/ensure_future和Task之间有什么区别?

33
kwarunek

注意:仅对 Python 3.7 有效(对于Python 3.5,请参阅 早期答案 )。

来自官方文档:

asyncio.create_task (在Python 3.7中添加)是产生新任务的首选方法,而不是 ensure_future()


详情:

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

好吧,这两个封装函数都可以帮助你调用 BaseEventLoop.create_task 。唯一的区别是ensure_future接受任何 awaitable object并帮助您将其转换为Future。您还可以在event_loop中提供自己的ensure_future参数。根据您是否需要这些功能,您可以选择使用哪个包装器。

10
Yeo

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

3
ospider