Разница между coroutine и future / task в Python 3.5?

предположим, у нас есть фиктивная функция:

async def foo(arg):
    result = await some_remote_call(arg)
    return result.upper()

в чем разница между:

coros = []
for i in range(5):
    coros.append(foo(i))

loop = get_event_loop()
loop.run_until_complete(wait(coros))

и:

from asyncio import ensure_future

futures = []
for i in range(5):
    futures.append(ensure_future(foo(i)))

loop = get_event_loop()
loop.run_until_complete(wait(futures))

Примечание: пример возвращает результат, но это не фокус вопроса. Когда возвращаемое значение имеет значение, используйте gather() вместо wait().

независимо от возвращаемого значения, я ищу ясность на ensure_future(). wait(coros) и wait(futures) оба запускают корутину, поэтому, когда и почему должна быть корутина завернутый в ensure_future?

в принципе, как правильно (tm) запускать кучу неблокирующих операций с использованием Python 3.5 async?

для дополнительного кредита, что, если я хочу пакетировать звонки? Например, мне нужно позвонить some_remote_call(...) 1000 раз, но я не хочу раздавить веб-сервер/базу данных/etc с 1000 одновременными подключениями. Это выполнимо с пулом потоков или процессов, но есть ли способ сделать это с помощью asyncio?

3 ответов


корутина-это функция генератора, которая может как давать значения, так и принимать значения извне. Преимущество использования coroutine заключается в том, что мы можем приостановить выполнение функции и возобновить ее позже. В случае сетевой операции имеет смысл приостановить выполнение функции, пока мы ждем ответа. Мы можем использовать это время для выполнения других функций.

будущее похоже на Promise объекты из Javascript. Это как держатель места для ценности это материализуется в будущем. В вышеупомянутом случае, ожидая сетевого ввода - вывода, функция может дать нам контейнер, обещание, что она заполнит контейнер значением, когда операция завершится. Мы держимся за будущий объект, и когда он будет выполнен, мы можем вызвать метод для получения фактического результата.

Прямого Ответа: не нужно ensure_future если вам не нужны результаты. Они хороши, если вам нужны результаты или получения случались исключения.

Дополнительные Кредиты: я бы выбрал run_in_executor и передать Executor экземпляр для управления количеством рабочих max.

пояснения и примеры кодов

в первом примере вы используете coroutines. The wait функция принимает кучу сопрограммы и объединяет их вместе. Так что wait() завершает работу, когда все сопрограммы исчерпаны (завершено/завершено возвращение всех значений).

loop = get_event_loop() # 
loop.run_until_complete(wait(coros))

на run_until_complete способ убедиться, что петли жив до работы. Обратите внимание, что в этом случае вы не получаете результаты асинхронного выполнения.

во втором примере вы используете ensure_future функция для обертывания корутина и возврата Task объект, который является своего рода Future. Корутину планируется выполнить в основном цикле событий при вызове ensure_future. Возвращаемый объект future / task не еще есть значение, но со временем, когда сетевые операции закончатся, будущий объект будет содержать результат операции.

from asyncio import ensure_future

futures = []
for i in range(5):
    futures.append(ensure_future(foo(i)))

loop = get_event_loop()
loop.run_until_complete(wait(futures))

так, в данном примере, мы делаем то же самое, за исключением мы используем фьючерсы а не только с помощью сопрограмм.

давайте рассмотрим пример использования asyncio/coroutines / futures:

import asyncio


async def slow_operation():
    await asyncio.sleep(1)
    return 'Future is done!'


def got_result(future):
    print(future.result())

    # We have result, so let's stop
    loop.stop()


loop = asyncio.get_event_loop()
task = loop.create_task(slow_operation())
task.add_done_callback(got_result)

# We run forever
loop.run_forever()

здесь мы использовали create_task метод

конечно, вы можете найти больше деталей на официальном руководстве: https://docs.python.org/3/library/asyncio.html


комментарий Винсента, связанный с https://github.com/python/asyncio/blob/master/asyncio/tasks.py#L346, что показывает, что wait() оборачивает корутину в ensure_future() для вас!

другими словами, нам нужно будущее, и coroutines будут тихо преобразованы в них.

Я буду обновлять этот ответ, когда я найти окончательное объяснение того, как партии сопрограммы/фьючерсы.


из BDFL [2013]

задачи

  • это корутина, завернутая в будущее
  • class Task является подклассом класса Future
  • С ждут слишком!

  • чем он отличается от голой корутины?
  • он может добиться прогресса, не дожидаясь его
    • пока вы ждете чего-то другого, т. е.
      • ждут [something_else]

имея это в виду,ensure_future имеет смысл как имя для создания задачи, так как результат будущего будет вычислен независимо от того, будете ли вы ждут это (пока вы чего-то ждете). Это позволяет циклу событий выполнить вашу задачу, пока вы ждете других вещей. Обратите внимание, что в Python 3.7 create_task является предпочтительным способом обеспечить будущее.

Примечание: я изменил "выход из" в слайдах Гвидо на "ожидание" здесь для современности.