【问题标题】:asyncio.ensure_future vs. BaseEventLoop.create_task vs. simple coroutine?asyncio.ensure_future vs. BaseEventLoop.create_task vs. 简单协程?
【发布时间】:2016-07-20 11:13:13
【问题描述】:

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

【问题讨论】:

    标签: python python-3.x python-3.5 coroutine python-asyncio


    【解决方案1】:

    create_task()

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

    ensure_future()

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

    如您所见,create_task 更加具体。


    async 没有 create_task 或 ensure_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 是多余的。

    类似问题What's the difference between loop.create_task, asyncio.async/ensure_future and Task?

    【讨论】:

      【解决方案2】:

      实际信息:

      从 Python 3.7 开始 asyncio.create_task(coro) 高级函数 was added 用于此目的。

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


      旧信息:

      ensure_futurecreate_task

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

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

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

      更新:

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

      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 相比,它是更高级别的功能(讨论contains 一些想法,例如添加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 __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() 来感受不同。

      【讨论】:

      • 根据 Guido 的说法,如果你真的需要一个通常不需要的任务对象,你应该使用 create_taskgithub.com/python/asyncio/issues/477#issuecomment-268709555
      • @bj0 感谢您提供此链接。我更新了答案,添加了此讨论中的信息。
      • ensure_future 是否会自动将创建的Task 添加到主事件循环中?
      • @laycat 我们需要在msg() 中的await 在第二次调用时将控制权返回给事件循环。事件循环一旦接收控制就可以启动long_operation()。它是为了演示ensure_future如何启动协程与当前执行流程并发执行。
      • @garej 如果你放弃它,你不应该看到最后一个输出long_operation finished,因为main()(和整个事件循环)早于long_operation() 任务完成。如果你在 Jupyter 中运行脚本,它猜测可能不是这样,但无论如何await task 的想法是我们需要它来等待任务完成。
      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

        注意:仅对 Python 3.7 有效(对于 Python 3.5,请参阅earlier answer)。

        来自官方文档:

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


        详情:

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

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

        【讨论】:

        • 我认为还有一个未记录的区别:如果您尝试在运行循环之前调用 asyncio.create_task ,那么您将遇到问题,因为 asyncio.create_task 需要一个正在运行的循环。但是,在这种情况下,您可以使用 asyncio.ensure_future,因为不需要运行循环。
        猜你喜欢
        • 2019-02-12
        • 2020-02-17
        • 2012-03-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多