Допустим, у нас есть фиктивная функция:
async def foo(arg):
result = await some_remote_call(arg)
return result.upper()
Какая разница между:
import asyncio
coros = []
for i in range(5):
coros.append(foo(i))
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(coros))
И:
import asyncio
futures = []
for i in range(5):
futures.append(asyncio.ensure_future(foo(i)))
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(futures))
Примечание . Пример возвращает результат, но вопрос не в этом. Когда значение возврата имеет значение, используйтеgather()
вместо wait()
.
Независимо от возвращаемого значения, я ищу ясности ensure_future()
. wait(coros)
иwait(futures)
оба запускают сопрограммы, поэтому когда и почему сопрограмму следует обернуть вensure_future
?
В принципе, каков правильный способ (TM) запускать кучу неблокирующих операций с использованием Python 3.5 async
?
Что делать, если я хочу группировать звонки в качестве дополнительного кредита? Например, мне нужно позвонить some_remote_call(...)
1000 раз, но я не хочу раздавить веб-сервер / базу данных и т. Д. 1000 одновременных подключений. Это можно сделать с потоком или пулом процессов, но есть ли способ сделать это с помощью asyncio
?
Обновление 2020 (Python 3.7+) : не используйте эти фрагменты. Вместо этого используйте:
import asyncio
async def do_something_async():
tasks = []
for i in range(5):
tasks.append(asyncio.create_task(foo(i)))
await asyncio.gather(*tasks)
def do_something():
asyncio.run(do_something_async)
Также рассмотрите возможность использования Trio , надежной сторонней альтернативы asyncio.
источник
ensure_future()
? И если мне нужен результат, нельзя ли просто использоватьrun_until_complete(gather(coros))
?ensure_future
планирует выполнение сопрограммы в цикле событий. Так что я бы сказал, да, это необходимо. Но, конечно, вы можете запланировать сопрограммы, используя и другие функции / методы. Да, вы можете использоватьgather()
- но gather будет ждать, пока соберутся все ответы.gather
иwait
фактически обернуть данные сопрограммы как задачи с использованиемensure_future
(см. Источники здесь и здесь ). Так что нет никакого смысла использоватьensure_future
заранее, и это не имеет никакого отношения к получению результата или нет.ensure_future
естьloop
аргумент, поэтому нет никаких оснований для использованияloop.create_task
болееensure_future
. Иrun_in_executor
не будет работать с сопрограммами, вместо этого следует использовать семафор .create_task
overensure_future
, см. docs . Цитатаcreate_task() (added in Python 3.7) is the preferable way for spawning new tasks.
Простой ответ
async def
) НЕ запускает ее. Он возвращает объекты сопрограммы, например, функция генератора возвращает объекты генератора.await
извлекает значения из сопрограмм, т.е. "вызывает" сопрограммуeusure_future/create_task
запланировать запуск сопрограммы в цикле событий на следующей итерации (не дожидаясь их завершения, как поток демона).Некоторые примеры кода
Сначала проясним некоторые термины:
async def
;Случай 1,
await
в сопрограммеМы создаем две сопрограммы,
await
одну и используемcreate_task
для запуска другой.вы получите результат:
Объясните:
task1 выполнялась напрямую, а task2 выполнялась на следующей итерации.
Случай 2, передача управления циклу событий
Если мы заменим основную функцию, мы увидим другой результат:
вы получите результат:
Объясните:
При вызове
asyncio.sleep(1)
элемент управления возвращался в цикл событий, и цикл проверяет наличие задач для запуска, а затем запускает задачу, созданную с помощьюcreate_task
.Обратите внимание, что мы сначала вызываем функцию сопрограммы, но не
await
ее, поэтому мы просто создали одну сопрограмму, а не запускали ее. Затем мы снова вызываем функцию сопрограммы и оборачиваем ее вcreate_task
вызов, creat_task фактически планирует запуск сопрограммы на следующей итерации. Итак, в результатеcreate task
выполняется доawait
.Фактически, суть здесь в том, чтобы вернуть управление циклу, которое вы могли бы использовать
asyncio.sleep(0)
для получения того же результата.Под капотом
loop.create_task
собственно звонкиasyncio.tasks.Task()
, которые будут звонитьloop.call_soon
. Иloop.call_soon
поставлю задачуloop._ready
. Во время каждой итерации цикла он проверяет все обратные вызовы в loop._ready и запускает его.asyncio.wait
,asyncio.ensure_future
иasyncio.gather
фактически звонятloop.create_task
прямо или косвенно.Также обратите внимание на документы :
источник
await task2
звонка можно прояснить. В обоих примерах вызов loop.create_task () - это то, что планирует task2 в цикле событий. Таким образом, в обоих exs вы можете удалить,await task2
и в конечном итоге задача2 будет запущена. В ex2 поведение будет идентичным, посколькуawait task2
я считаю, что это просто планирование уже завершенной задачи (которая не будет запускаться во второй раз), тогда как в ex1 поведение будет немного другим, так как task2 не будет выполняться до тех пор, пока main не будет завершен. Чтобы увидеть разницу, добавьтеprint("end of main")
в конце основной строки ex1Комментарий Винсента, связанный с https://github.com/python/asyncio/blob/master/asyncio/tasks.py#L346 , показывает, что
wait()
сопрограммы обертываютensure_future()
за вас!Другими словами, нам действительно нужно будущее, и сопрограммы будут незаметно преобразованы в них.
Я обновлю этот ответ, когда найду исчерпывающее объяснение того, как пакетировать сопрограммы / фьючерсы.
источник
c
,await c
эквивалентноawait create_task(c)
?Из BDFL [2013]
Задачи
Имея это в виду,
ensure_future
имеет смысл использовать имя для создания Задачи, поскольку результат Будущего будет вычислен независимо от того, ожидаете вы его или нет (пока вы чего-то ждете). Это позволяет циклу событий завершить вашу задачу, пока вы ждете других вещей. Обратите внимание, что Python 3.7create_task
является предпочтительным способом обеспечения будущего .Примечание: я изменил «yield from» на слайдах Guido на «await» для современности.
источник