asyncio.ensure_future проти BaseEventLoop.create_task проти простої програми?


96

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

Відповіді:


116

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

Починаючи з 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лише в тому випадку, якщо ви збираєтесь реалізувати власний тип циклу подій.

Оновлено:

@ bj0 вказав на відповідь Гідо на цю тему:

Сенс у ensure_future()тому, що якщо у вас є щось, що може бути або підпрограмою, або a Future(останній включає в себе а, Taskтому що це підклас Future), і ви хочете мати можливість викликати на ньому метод, який визначений лише Future(можливо, про єдиний корисний приклад буття cancel()). Коли це вже Future(або Task), це нічого не робить; коли це супровід, він загортає його в Task.

Якщо ви знаєте, що у вас є програма, і ви хочете, щоб це було заплановано, правильним API є create_task(). Єдиний час, коли вам слід дзвонити, ensure_future()- це коли ви надаєте API (як і більшість власних API API), який приймає або програму, або а, Futureі вам потрібно зробити щось для цього, що вимагає, щоб ви мали Future.

і пізніше:

Зрештою, я все-таки вважаю, що ensure_future()це належним чином незрозуміле ім'я для рідко потрібної функції. Створюючи завдання зі спільної програми, ви повинні використовувати відповідну назву loop.create_task(). Може, для цього повинен бути псевдонім asyncio.create_task()?

Для мене це дивно. Моя основна мотивація використовувати ensure_futureвесь час - це функція вищого рівня порівняно з членом циклу create_task(обговорення містить деякі ідеї, такі як додавання asyncio.spawnчи asyncio.create_task).

Я також можу зазначити, що на мою думку, досить зручно використовувати універсальну функцію, яка може справлятись лише з будь-якими, Awaitableа не з супроводуми.

Однак відповідь Гвідо зрозуміла: "Створюючи завдання за допомогою програми, ви повинні використовувати відповідне ім'я 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()відчути різницю.


3
За словами Гвідо, ви повинні використовувати, create_taskякщо вам дійсно потрібен об'єкт завдання, який вам зазвичай не потрібен: github.com/python/asyncio/isissue/477#issuecomment-268709555
bj0

@ bj0 дякую за це посилання. Я оновив відповідь, додавши інформацію з цього обговорення.
Михайло Герасимов

це ensure_futureавтоматично додає створений Taskв основний цикл подій?
AlQuemist

@AlQuemist кожна створена вами програма, майбутнє або завдання автоматично прив'язуються до певного циклу подій, де вони будуть виконані пізніше. За замовчуванням це поточний цикл подій для поточного потоку, але ви можете вказати інший цикл подій, використовуючи loopаргумент ключового слова ( див. Підпис secure_future ).
Михайло Герасимов

2
@laycat нам потрібно awaitв msg()повернути управління цикл подій на другий виклик. Цикл подій після отримання контролю можна буде запустити long_operation(). Це зроблено, щоб продемонструвати, як ensure_futureзапускається програма для виконання одночасно з поточним потоком виконання.
Михайло Герасимов

45

create_task()

  • приймає супроводи,
  • повертає завдання,
  • він викликається в контексті циклу.

ensure_future()

  • приймає ф'ючерси, програми, очікувані об'єкти,
  • повертає Завдання (або Майбутнє, якщо Майбутнє пройдено).
  • якщо даний аргумент є спільною програмою, яку він використовує create_task,
  • цикл об'єкт може бути переданий.

Як бачите, create_task є більш конкретним.


async функція без create_task або secure_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 / secure_future та Task?


13

Примітка. Дійсний лише для 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 процедур і разом подали в цикл. тому лише останній дає вихід випадковим чином.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.