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


96

我看过一些关于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变量的所有三个变体都可以达到相同的结果。我可以看到的唯一区别是,在第三个变体中,执行是乱序的(在大多数情况下不重要)。还有其他区别吗?在某些情况下,我不能仅使用最简单的变体(协程的简单列表)吗?

Answers:


116

实际信息:

从Python 3.7开始,为此添加asyncio.create_task(coro)高级功能。

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


旧信息:

ensure_futurecreate_task

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

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

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

更新:

@ bj0指向Guido对此主题的回答

的要点ensure_future()是,如果您拥有某种可能是协程或a的东西Future(后者包含a,Task因为这是的子类Future),并且您希望能够在其上调用仅在其上定义的方法Future(可能是唯一的)有用的示例cancel())。当它已经是Future(或Task)时,则不执行任何操作;当它是一个协程时,它将它包裹在一个Task

如果您知道有一个协程,并且希望对其进行调度,则使用的正确API是create_task()。唯一应该调用的时间ensure_future()是在提供接受协程或a的API(如asyncio自己的大多数API)时,Future您需要对其进行一些操作,要求您拥有a Future

然后:

最后,我仍然相信这ensure_future()是一个很少需要的功能的适当模糊的名称。从协程创建任务时,应使用适当命名的 loop.create_task()。也许应该为此起别名 asyncio.create_task()

我感到惊讶。我一直使用的主要动机ensure_future是,与loop的成员相比,它是更高层的函数create_task(讨论中包含了诸如add asyncio.spawn或的想法asyncio.create_task)。

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

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

什么时候应该将协程包裹在任务中?

将协程包装在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 __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()来感受不同。


3
根据Guido的说法,create_task如果您确实需要一个通常不需要的任务对象,则应该使用:github.com/python/asyncio/issues/477#issuecomment-268709555
bj0

@ bj0谢谢您的链接。我更新了此讨论中添加信息的答案。
米哈伊尔·杰拉西莫夫

ensure_future自动将所创建Task的主要事件循环?
AlQuemist

@AlQuemist您创建的每个协程,未来或任务都将自动绑定到某个事件循环,稍后将在其中执行。默认情况下,它是当前线程的当前事件循环,但是您可以使用loop关键字参数指定其他事件循环(请参见sure_future签名)。
米哈伊尔·盖拉西莫夫

2
@laycat我们需要awaitmsg()第二次调用时将控制权返回给事件循环。事件循环一旦收到控制权便可以启动long_operation()。它演示了如何ensure_future启动协程与当前执行流程同时执行。
米哈伊尔·杰拉西莫夫

45

create_task()

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

ensure_future()

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

如您所见,create_task更具体。


async 没有create_task或sure_future的函数

简单的调用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有什么区别?


13

注意:仅对Python 3.7有效(对于Python 3.5,请参考前面的答案)。

从官方文档:

asyncio.create_task(在Python 3.7中添加)是生成新任务的替代方法,而不是ensure_future()


详情:

因此,现在,在Python 3.7及更高版本中,有2个顶级包装函数(相似但不同):

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


我认为还有一个未记录的差异:如果您尝试在运行循环之前调用asyncio.create_task,则将遇到问题,因为asyncio.create_task正在运行循环。但是,在这种情况下,可以使用asyncio.ensure_future,因为不需要运行循环。
coelhudo

4

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.