Разница между 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 раз, но я не хочу давить на базе Web-сервера//и т. д. с 1000 одновременных подключений. Это выполнимо с потоком или пулом процессов, но есть ли способ сделать это с asyncio?

3 73

3 ответа:

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

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

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

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

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

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

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

The 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))

давайте посмотрим на пример о том, как использовать ввода-вывода/сопрограммы/фьючерсы:

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://github.com/python/asyncio/blob/master/asyncio/tasks.py#L346, что показывает, что wait() обертывает сопрограммы в ensure_future() для вас!

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

из BDFL [2013]

задачи

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

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

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

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