Страницы

Поиск по вопросам

среда, 22 января 2020 г.

ASYNCIO.Отличие Tasks от Future

#python #asyncio


Проблема

Вот уже несколько дней не могу понять разницу между объектами Future и Task. Точнее,
не уверен что до конца понимаю что и где используется. 

Мое понимание

Из документации следует, что в asyncio существует три вида объектов курутины, задачи(Task)
и футуры(Future)(назовем их так).
Задачи - это курутины выполнение которых запланировано в eventloop.


  When a coroutine is wrapped into a Task with functions like
  asyncio.create_task() the coroutine is automatically scheduled to run
  soon


Футуры - это некий объект в котором хранится возможный результат асинхронной операции.


  A Future represents an eventual result of an asynchronous operation


т.е. для того, что бы запустить курутину на выполнение в асинхронном режиме необходимо
использовать asyncio.create_task, но зачем тогда нам ensure_future?, который фигурирует
во всех примерах кода по asyncio. На англоязычном stackoverflow нашел смежный вопрос,
но как мне показалось нормального ответа там и нет. Ниже представлен код в котором
я использую различные варианты запуска курутин - результат всегда одинаков:

import asyncio
import time

async def fetch(n):
    await asyncio.sleep(n)
    print(time.time())
    return n

loop = asyncio.get_event_loop()
# coros = [asyncio.ensure_future(fetch(i)) for i in range(2)]
# coros = [fetch(i) for i in range(3)]
coros = [loop.create_task(fetch(i)) for i in range(2)]
results = loop.run_until_complete(asyncio.gather(*coros))
print(results)


С вариантом 2 и 3 понятно, так как в соответствии с документацией gather преобразует
курутины в задачи, а вот зачем тогда вообще используют ensure_future. Так же в документации
сказано, что лучше использовать create_task.


  See also the create_task() function which is the preferred way for
  creating new Tasks.


Если подытожить, то хотелось бы услышать простое объяснение что такое футура, чем
она отличается от задачи и где используется.

p.s. Пока писал вопрос нашел вот такую статью, но все равно не понятно) и еще один
смежный вопрос
    


Ответы

Ответ 1



Не совсем понятно, что именно непонятно, поэтому я попробую упростить ответ максимально, прибегнув при этом к грубым допущениям. Да простят меня коллеги. Сопрограмма (coroutine) — результат вызова асинхронной функции, представляющий собой выполнение этой функции, способное приостанавливаться. Так как в общем случае невозможно определить сколько раз и на какое время выполнение будет приостановлено, невозможно и сказать когда оно будет закончено. Ваш код может либо ждать завершения выполнения сопрограммы с помощью оператора await, либо поручить ожидание циклу событий и продолжить свой выполнение. В первом случае async def callee(): print('Hello') async def caller(): await callee() print('World') выполнение caller приостановится до выполнения callee. В этот момент какие-то другие операции в каких-то других сопрограммах могут продолжаться, но caller будет ждать там, где выполнил await. Во втором случае async def callee(): print('Hello') async def caller(): asyncio.create_task(callee()) print('World') caller сразу же продолжит свою работу. Строка "World" будет выведена раньше, чем "Hello". Здесь мы видим, что caller поставил циклу событий задачу выполнить сопрограмму callee. Но что если, callee будет возвращать какое-то значение, которое нужно вызывающей стороне, но не прямо сейчас, а когда будет готово? Вот тут-то на сцену выходят футуры. Футура (Future) - будущий результат выполнения сопрограммы. Метод ensure_future поручает циклу событий выполнить сопрограмму и сразу же, в момент вызова, возвращает футуру, в которой будет значение, но неизвестно когда. Вызывающая сторона может подождать выполнения футуры так же, как ожидало саму сопрограмму async def callee(): return 'Hello' async def caller(): loop = asyncio.get_event_loop() future = loop.ensure_future(callee()) result = await future print(result + ' World') Или может заняться своими делами, периодически проверяя готовность async def caller(): loop = asyncio.get_event_loop() future = loop.ensure_future(callee()) while not future.done(): # Какие-нибудь циклические дела print(future.result() + ' World') Или установить на футуру колбэк async def caller(): loop = asyncio.get_event_loop() future = loop.ensure_future(callee()) future.add_done_callback(lambda f: print(f.result() + ' World')) # какие-нибудь другие важные дела Или может собрать их в список и ждать все. Или не все, а только ту, которая будет выполнена первой, а остальные проигнорировать. Или передать футуру другой сопрограмме, а самой заняться каким-нибудь другим делом. В общем, это "очень полезный горшок, куда можно класть какие хочешь вещи". Осталось только разобраться, чем отличаются футуры от задач. Ничем не отличаются, по большому счёту. Класс Task - это наследник класса Future. А существенная разница между asyncio.create_task() и loop.ensure_future() только в том, что первой не было до Python 3.7. Подытоживая: Task - это задача, поставленная циклу событий, на выполнение coroutine, одновременно являющаяся Future, которая представляет собой результат выполнения Task когда-нибудь в будущем.

Комментариев нет:

Отправить комментарий