ввода-вывода.обеспечьте будущее против BaseEventLoop.создать задачу против простой сопрограммы?


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

4 56

4 ответа:

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

create_task()

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

ensure_future()

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

как вы можете см. create_task более конкретно.


async функция без create_task или ensure_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 избыточна.

аналогичный вопрос в чем разница между петлей.create_task, ввода-вывода.async/ensure_future и Задача?

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

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

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

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


деталь:

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

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