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

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

I’ve seen several basic Python 3.5 tutorials on asyncio doing the same operation in various flavours. In this code:

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)

All the three variants above that define the futures variable achieve the same result; the only difference I can see is that with the third variant the execution is out of order (which should not matter in most cases). Is there any other difference? Are there cases where I can’t just use the simplest variant (plain list of coroutines)?


回答 0

实际信息:

从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()来感受不同。

Actual info:

Starting from Python 3.7 asyncio.create_task(coro) high-level function was added for this purpose.

You should use it instead other ways of creating tasks from coroutimes. However if you need to create task from arbitrary awaitable, you should use asyncio.ensure_future(obj).


Old info:

ensure_future vs create_task

ensure_future is a method to create Task from coroutine. It creates tasks in different ways based on argument (including using of create_task for coroutines and future-like objects).

create_task is an abstract method of AbstractEventLoop. Different event loops can implement this function different ways.

You should use ensure_future to create tasks. You’ll need create_task only if you’re going to implement your own event loop type.

Upd:

@bj0 pointed at Guido’s answer on this topic:

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.

and later:

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()?

It’s surprising to me. My main motivation to use ensure_future all along was that it’s higher-level function comparing to loop’s member create_task (discussion contains some ideas like adding asyncio.spawn or asyncio.create_task).

I can also point that in my opinion it’s pretty convenient to use universal function that can handle any Awaitable rather than coroutines only.

However, Guido’s answer is clear: “When creating a task from a coroutine you should use the appropriately-named loop.create_task()

When coroutines should be wrapped in tasks?

Wrap coroutine in a Task – is a way to start this coroutine “in background”. Here’s example:

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())

Output:

first
long_operation started
second
long_operation finished

You can replace asyncio.ensure_future(long_operation()) with just await long_operation() to feel the difference.


回答 1

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有什么区别?

create_task()

  • accepts coroutines,
  • returns Task,
  • it is invoked in context of the loop.

ensure_future()

  • accepts Futures, coroutines, awaitable objects,
  • returns Task (or Future if Future passed).
  • if the given arg is a coroutine it uses create_task,
  • loop object can be passed.

As you can see the create_task is more specific.


async function without create_task or ensure_future

Simple invoking async function returns coroutine

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

And since the gather under the hood ensures (ensure_future) that args are futures, explicitly ensure_future is redundant.

Similar question What’s the difference between loop.create_task, asyncio.async/ensure_future and Task?


回答 2

注意:仅对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。而且,根据您是否需要这些功能,您可以简单地选择要使用的包装器。

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

From the official docs:

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


Detail:

So now, in Python 3.7 onwards, there are 2 top-level wrapper function (similar but different):

Well, utlimately both of these wrapper functions will help you call BaseEventLoop.create_task. The only difference is ensure_future accept any awaitable object and help you convert it into a Future. And also you can provide your own event_loop parameter in ensure_future. And depending if you need those capability or not, you can simply choose which wrapper to use.


回答 3

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

for your example, all the three types execute asynchronously. the only difference is that, in the third example, you pre-generated all 10 coroutines, and submitted to the loop together. so only the last one gives output randomly.