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

101

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

крестоносцы
источник

Ответы:

121

Актуальная информация:

Начиная с Python 3.7 для этого была добавленаasyncio.create_task(coro) высокоуровневая функция .

Вы должны использовать его вместо других способов создания задач из сопрограмм. Однако, если вам нужно создать задачу из произвольного объекта ожидания, вы должны использовать asyncio.ensure_future(obj).


Старая информация:

ensure_future против create_task

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

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

Вы должны использовать ensure_futureдля создания задач. Вам понадобится create_taskтолько в том случае, если вы собираетесь реализовать собственный тип цикла событий.

Upd:

@ bj0 указал на ответ Гвидо по этой теме:

Дело в ensure_future()том, что если у вас есть что-то, что может быть либо сопрограммой, либо Future(последний включает в себя, Taskпотому что это подкласс Future), и вы хотите иметь возможность вызывать на нем метод, который определен только Future(вероятно, о единственном полезный пример существа cancel()). Когда это уже Future(или Task), это ничего не делает; когда это сопрограмма, она оборачивается в Task.

Если вы знаете, что у вас есть сопрограмма, и вы хотите, чтобы она была запланирована, правильный API для использования - create_task(). Единственный раз, когда вы должны звонить, ensure_future()- это когда вы предоставляете API (например, большинство собственных API asyncio), который принимает либо сопрограмму, либо a, Futureи вам нужно что-то сделать с ним, что требует наличия Future.

и позже:

В конце концов, я все еще считаю, что ensure_future()это достаточно неясное название для редко необходимой части функциональности. При создании задачи из сопрограммы вы должны использовать файл с соответствующим именем loop.create_task(). Может быть, для этого должен быть псевдоним asyncio.create_task()?

Для меня это удивительно. Моей основной мотивацией для использования ensure_futureвсегда было то, что это функция более высокого уровня по сравнению с членом цикла create_task(обсуждение содержит некоторые идеи, такие как добавление asyncio.spawnили asyncio.create_task).

Также могу отметить, что, на мой взгляд, довольно удобно использовать универсальную функцию, которая может обрабатывать любые, Awaitableа не только сопрограммы.

Однако ответ Гвидо ясен: «При создании задачи из сопрограммы вы должны использовать соответствующий названный 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
По словам Гвидо, вы должны использовать, create_taskесли вам действительно нужен объект задачи, который обычно вам не нужен: github.com/python/asyncio/issues/477#issuecomment-268709555
bj0
@ bj0 спасибо за ссылку. Я обновил ответ, добавив информацию из этого обсуждения.
Михаил Герасимов
это ensure_futureавтоматически добавляет созданный Taskв основной цикл событий?
AlQuemist 05
@AlQuemist - каждая сопрограмма, будущее или задача, которую вы создаете, автоматически привязывается к некоторому циклу событий, где она будет выполняться позже. По умолчанию это текущий цикл событий для текущего потока, но вы можете указать другой цикл событий, используя loopаргумент ключевого слова ( см. Подпись sure_future ).
Михаил Герасимов
2
@laycat нам нужно awaitв msg()вернуть управление цикл событий на второй вызов. Цикл событий после получения управления сможет запуститься long_operation(). Это сделано для демонстрации того, как ensure_futureзапускать сопрограмму одновременно с текущим потоком выполнения.
Михаил Герасимов
47

create_task()

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

ensure_future()

  • принимает фьючерсы, сопрограммы, ожидаемые объекты,
  • возвращает Task (или Future, если Future прошло).
  • если данный аргумент является сопрограммой, которую он использует 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), что аргументы являются фьючерсами, явно ensure_futureизбыточно.

Аналогичный вопрос В чем разница между loop.create_task, asyncio.async / sure_future и Task?

Кварунек
источник
14

Примечание. Действительно только для Python 3.7 (для Python 3.5 см. Предыдущий ответ ).

Из официальных документов:

asyncio.create_task(добавлен в Python 3.7) - предпочтительный способ создания новых задач вместо ensure_future().


Деталь:

Итак, теперь, в Python 3.7 и новее, есть 2 функции-оболочки верхнего уровня (похожие, но разные):

Что ж, в конечном итоге обе эти функции-оболочки помогут вам вызвать BaseEventLoop.create_task. Единственная разница - ensure_futureпринять любой awaitableобъект и помочь вам преобразовать его в будущее. А также вы можете указать свой собственный event_loopпараметр в ensure_future. И в зависимости от того, нужны ли вам эти возможности или нет, вы можете просто выбрать, какую оболочку использовать.

Йео
источник
Я думаю, что есть еще одно отличие, которое не задокументировано: если вы попытаетесь вызвать asyncio.create_task перед запуском цикла, у вас возникнет проблема, поскольку asyncio.create_task ожидает выполнения цикла. Однако в этом случае вы можете использовать asyncio.ensure_future, поскольку выполнение цикла не является обязательным.
coelhudo
4

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

паук
источник