Подтвердить что ты не робот

Asyncio.ensure_future vs. BaseEventLoop.create_task против простой сопрограммы?

Я видел несколько базовых руководств по Python 3.5 для asyncio, выполняющих ту же операцию в различных вариантах. В этом коде:

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, достигают того же результата; единственное различие, которое я вижу, заключается в том, что с третьим вариантом выполнение не соответствует порядку (что в большинстве случаев не имеет значения). Есть ли другая разница? Существуют ли случаи, когда я не могу просто использовать простейший вариант (обычный список сопрограмм)?

4b9b3361

Ответ 1

ensure_future vs create_task

ensure_future - метод создания Task из coroutine. Он создает задачу по-разному на основе аргумента (включая использование create_task для сопрограмм и объектов будущего).

create_task - абстрактный метод AbstractEventLoop. Различные циклы событий могут реализовать эту функцию по-разному.

Вы должны использовать ensure_future для создания задач. Вам понадобится 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(), чтобы почувствовать разницу.

Ответ 2

create_task()

  • принимает сопрограммы,
  • возвращает задачу,
  • он вызывается в контексте цикла.

ensure_future()

  • принимает фьючерсы, сопрограммы, ожидаемые объекты,
  • возвращает задачу (или будущее, если будущее прошло).
  • если данный arg является сопрограммой, она использует create_task,
  • объект цикла может быть передан.

Как вы видите, более конкретная конструкция create_task.


async функция без create_taks или обеспечения_future

Функция простого вызова async возвращает coroutine

>>> 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/security_future и Task?

Ответ 3

для вашего примера, все три типа выполняются асинхронно. единственное отличие состоит в том, что в третьем примере вы предварительно сгенерировали все 10 сопрограмм и отправили в цикл вместе. поэтому только последний дает выход случайным образом.