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