PYTHON ASYNCIO: цикл событий и FUTURE, TASK

Цикл событий (Event Loop)

Цикл событий – это цикл, который может: регистрировать задачи для выполнения, выполняет их, задерживает или даже отменяет их и обрабатывать различные события, связанные с этими операциями. Обычно мы планируем несколько асинхронных функций в цикле событий. Цикл запускает одну функцию, в то время как эта функция ожидает ввода-вывода, приостанавливает ее и запускает другую. Когда первая функция завершает ввод-вывод, она возобновляется. Таким образом, две или более функции могут работать совместно. Это главная цель цикла событий.

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

Futures и Tasks (Задачи)

Future – это объект, который должен иметь результат в будущем. Это такое себе обещание на получения результата выполнения, при этом может быть получен не только успешный результат, но и ошибка возникшая в результате выполнения.

Задача (Task) – это подкласс Future, который служит оберткой для корутины. Когда корутина завершается, результат Задания становится доступным.

Корутина – это способ приостановить функцию и периодически возвращать серию значений. Функция созданная как корутина может приостановить свое выполнение, используя в выражении ключевые слова yield, yield from или await (python 3.5+). Функция приостанавливается до тех пор, пока оператор yield не получит значение.

Объединение цикла событий и задач

Все предельно просто. Нам нужен цикл событий, и нам нужно зарегистрировать наши объекты Future / Task в цикле событий. Цикл будет планировать и запускать их. Мы можем добавить обратные вызовы к нашим объектам Future / Task, чтобы мы могли получать уведомления, когда появятся результаты выполнения.

Типовым примером можн считать использование корутин, которые оборачиваются Future и далее создается объект Task. Когда корутина выполняет оператор yield, она приостанавливается. Когда ей передается значение, она возобновляет свою работу. Когда кортина завершается return , задача завершена и получает результирующие значение. Любой связанный обратный вызов выполняется. Если корутина вызывает исключение, задача завершается с ошибкой.

Например

В примере, декоратор @asyncio.coroutine – преобразует генератор в корутину. Операция loop.create_task(slow_operation()) – создает задачу из корутины созданной вызовом slow_operation. Операция task.add_done_callback(got_result) – регистрирует обратный вызов для целей обработки результатов. Метод loop.run_until_complete(task) – запускает цикл событий, регистрирует в нем задачу и ждет ее завершение. После завершения задачи цикл событий завершается.

Функция run_until_complete отличный метод для управления циклом событий. Конечно возможен и такой вариант:

Цикл событий запускается в бесконечном режиме, но в обработчике результатов мы завершаем цикл.

Python asyncio: Future, Task and the Event Loop

Важно понимание, что в asyncio нет никакой магии при реализации неблокирующих задач. Во время реализации asyncio стоял отдельно в стандартной библиотеке, т.к. остальные модули предоставляли только блокирующую функциональность. Вы можете использовать модуль concurrent.futures для оборачивания блокирующих задач в потоки или процессы и получения Future для использования в asyncio. Несколько таких примеров доступны на GitHub.
Это, наверно, главный недостаток сейчас при использовании asyncio, однако уже есть несколько библиотек, помогающих решить эту проблему.

Интересные замечания:

Вам не нужна функция ensure_future если Вам не нужен результат и вы не хотите получать исключения возникшие во время выполнения.

Метод run_in_executor позволяет передать Executor и таким образом контролировать количество рабочих процессов.

Сравнение Promise из JavaScript и Future/Tast из Python

Стоит сразу заметить, что Promise в JS это не то же самое, что Future в Python. Куда более эквивалентны Promise в JS и Task в Python. При этом стоит понимать, что задача (Task) в python, состоит из Future и Corutine. Другими словами задача состоит из обещания вернуть какое-то значение и кода который ее выполнит. При этом главное отличие Future от Promise из JS, состоит в том, что Promise автоматически выполнится асинхронно, в то время как Future не выполняется вообще это просто контейнер в который значение будет помещено некоторым внешним кодом через метод set_result(), при этом Future сразу же вызовет зарегистрированные обратные вызовы (вызов регистрируются через метод add_done_callback())

В то время как Corutine основанная на тех же принципах, что и генераторы. Корутина вызывается из цикла обработки событий и возвращает объекты Future и при этом ожидает когда это событие будет завершено. После завершения в корутину может быть передано значение и получено из нее новый Future. (по анологии с циклом for и генератором который выбрасывет через yield значения). 

Выражение await практически идентично yield from, поэтому, ожидая другую сопрограмму, вы останавливаетесь, пока у этой сопрограммы не будет разрешен Future, и вы не получаете возвращаемое значение. Future является повторяемым в один такт, и его итератор возвращает фактическое Future – это примерно означает, что await future равно yield from future и yield future.

Task это Future, который фактически был запущен для вычисления и привязан к циклу событий. Так что это особый вид Future (класс Task является производным от класса Future), который связан с некоторым циклом событий и имеет некоторую корутину (сопрограмму), которая служит исполнителем Task.

Задача обычно создается объектом цикла события: вы предоставляете корутину (сопрограмму) циклу, она создает объект задачи и начинает выполнять итерацию по этой корутине, как описано выше. Как только корутина закончена, Future задачи разрешается (получает результат) с помощью возвращаемого значения корутины.

Видите ли, задача очень похожа на JS Promise – она ​​инкапсулирует фоновое задание и его результат.

Coroutine func – это фабрика корутин (сопрограмм), подобная функции генератора для генераторов. Обратите внимание на разницу между функцией корутин Python и асинхронной функцией Javascript – при вызове асинхронная функция JS создает Promise, и ее внутренний генератор немедленно начинает выполнятся, в то время как корутина Python ничего не делает, пока на ней не будет создана задача.

В Python весь код которому нужна какая-либо функция asyncio, должен всегда работает цикл обработки событий.
Однако, смешивать синхронный и асинхронный код довольно сложно – вся ваша программа должна быть асинхронной (но вы можете запускать куски синхронного кода в отдельных потоках через API asyncio threadpool API)

Обсуждение закрыто.