关于python:asyncio.ensure_future与BaseEventLoop.create_task与简单协程?

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

我看过一些关于asyncio的基本Python 3.5教程,以各种方式进行相同的操作。
在此代码中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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变量的三个变体都可以达到相同的结果。 我可以看到的唯一区别是,在第三个变体中,执行是乱序的(在大多数情况下不重要)。 还有其他区别吗? 在某些情况下,我不能只使用最简单的变体(协程的简单列表)吗?


实际信息:

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

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

旧信息:

ensure_futurecreate_task

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

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

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

更新:

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

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相比,它是更高级别的函数(讨论中包含添加asyncio.spawnasyncio.create_task之类的一些想法)。

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

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

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

将协程包装在任务中-是一种在后台启动协程的方法。例子如下:

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

输出:

1
2
3
4
first
long_operation started
second
long_operation finished

您可以仅用await long_operation()替换asyncio.ensure_future(long_operation())来感受不同。


create_task()

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

ensure_future()

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

如您所见,create_task更具体。


async函数,不带create_task或sure_future

简单调用async函数返回协程

1
2
3
4
5
>>> 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之间有什么区别?


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

从官方文档:

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

详情:

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

  • asyncio.create_task:直接调用event_loop.create_task(coro)。 (请参见源代码)
  • ensure_future如果它是协程,也可以调用event_loop.create_task(coro),只是确保返回类型为asyncio.Future。 (请参见源代码)。无论如何,由于Task的类继承(参考),它仍然是Future

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


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