Перейти к содержимому

Корутины python что это

  • автор:

Корутины в Python

Когда говорят «написать корутину», обычно подразумевают асинхронную функцию. Корутины можно ставить на паузу, чтобы дать другим функциям немного поработать. В этом заключается принцип асинхронности. О нём мы рассказывали в этой статье.

Давайте сразу рассмотрим пример асинхронной функции:

import asyncio async def count_to_three(): print("Веду отсчёт. 1") await asyncio.sleep(0) print("Веду отсчёт. 2") await asyncio.sleep(0) print("Веду отсчёт. 3") await asyncio.sleep(0) 

Очень похоже на обычную функцию, однако здесь есть два новых слова: async и await .

async говорит Питону о том, что мы пишем не просто функцию, а асинхронную функцию. Просто добавили async и всё, функция теперь асинхронная.

Второе слово — await. Оно прерывает исполнение функции, и возвращает управление программой наружу. После этого корутину можно запустить повторно, а затем еще и еще, и каждый раз она будет продолжать работу с того await , на котором прервалась ранее. Например, в функции count_to_three команда await встречается три раза, значит корутину можно вызвать четыре раза (да, не три!). Корутина будет работать до первого await, затем до второго, до третьего и на четвёртый раз выполнит остатки до конца.

Нельзя делать await None или await «Hello, World!» . Можно await только то, что так и называют — «awaitable».

await asyncio.sleep(0) — это команда корутине «Дай поработать другим!»

Сразу покажем, как это выглядит на практике:

coroutine_counter = count_to_three() print(coroutine_counter) # coroutine_counter.send(None) # Выведет "Веду отсчёт. 1" coroutine_counter.send(None) # Выведет "Веду отсчёт. 2" coroutine_counter.send(None) # Выведет "Веду отсчёт. 3" 

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

Чтобы запустить корутину, используют метод send() . При каждом запуске корутины этим методом она продолжает исполняться с последнего await , на котором она остановилась. Поэтому при новом запуске той же корутины срабатывает не тот же print , а следующий.

Нельзя просто .send() . Всегда нужно передавать какое-то значение. Об этом тоже расскажем позже. Пока что воспринимайте .send(None) как команду «продолжи выполнять корутину».

Когда корутина закончится?

Она остановится навсегда, когда закончатся все await или встретится return . Когда корутина заканчивается — она истощается и вызов .send() выдаёт ошибку:

coroutine_counter = count_to_three() coroutine_counter.send(None) # Выведет "Веду отсчёт. 1" coroutine_counter.send(None) # Выведет "Веду отсчёт. 2" coroutine_counter.send(None) # Выведет "Веду отсчёт. 3" coroutine_counter.send(None) # Выбросит ошибку StopIteration 

Если мы хотим запустить наш счётчик сначала, придётся создать новую корутину, вызвав count_to_three() :

coroutine_counter = count_to_three() coroutine_counter.send(None) # Выведет "Веду отсчёт. 1" coroutine_counter.send(None) # Выведет "Веду отсчёт. 2" coroutine_counter_new = count_to_three() coroutine_counter_new.send(None) # Снова выведет "Веду отсчёт. 1", новая корутина 

Обычно заранее не известно сколько await будет до момента «истощения», поэтому исключение приходится «перехватывать»:

coroutine_counter = count_to_three() while True: try: coroutine_counter.send(None) # В четвёртый раз здесь вылетит StopIteration except StopIteration: break 

Исключение StopIteration возникает всего один раз. Если после него попробовать запустить корутину ещё раз, то поднимется другое исключение — RuntimeError , и оно уже будет считаться ошибкой. О том как работать с исключениями читайте в статье про try except.

Нельзя запускать истощённую корутину.

Добиваемся асинхронности

С корутинами разобрались, останавливать их научились. А зачем.

Корутины позволят вашему коду работать асинхронно, т.е. делать несколько вещей одновременно. Допустим, вы решили скачать несколько файлов. Обычный, синхронный код скачивает файлы по-очереди. Сначала первый файл целиком, затем второй, тоже целиком. Асинхронный код качает файлы одновременно, по кусочкам. Приведём пример скачивания двух файлов:

async def download_file(url): # здесь происходит какая-то логика со скачиванием файла image_downloader = download_file('https://www.some-images.com/image1.jpg') music_downloader = download_file('https://www.music-site.com/artist/album/song5.mp3') coroutines = [music_downloader, image_downloader] while True: for coroutine in coroutines.copy(): try: coroutine.send(None) except StopIteration: coroutines.remove(coroutine) if len(coroutines) == 0: break 

Разберём как работает код:

  1. Мы создали 2 корутины: image_downloader и music_downloader . Первая качает картинку по ссылке https://www.some-images.com/image1.jpg , вторая — музыку по ссыке https://www.music-site.com/artist/album/song5.mp3 .
  2. Мы положили их в список coroutines
  3. В бесконечном цикле мы по очереди запускаем все корутины из списка. Если вышла ошибка StopIteration — корутина истощилась, т.е. файл скачан. Убираем её из списка, корутина больше запускаться не будет.
  4. Чтобы итерация по списку coroutines не сбивалась после удаления элемента из него итерируем не по оригиналу, а по копии coroutines.copy() .
  5. Если список с корутинами закончился (его длина равна нулю), пора заканчивать и бесконечный цикл, потому что все файлы скачаны.

Передать параметры в асинхронную функцию

В плане аргументов асинхронные функции ничем не отличаются от обычных. Доработаем пример со счетчиком и вместо async def count_to_three напишем универсальную функцию async def count :

import asyncio async def count(limit=3): for step in range(1, limit+1): print("Веду отсчёт.", step) await asyncio.sleep(0) coroutine = count(5) while True: coroutine.send(None) 
Веду отсчёт. 1 Веду отсчёт. 2 Веду отсчёт. 3 Веду отсчёт. 4 Веду отсчёт. 5 Traceback (most recent call last): File "", line 2, in StopIteration 

Попробуйте бесплатные уроки по Python

Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.

Переходите на страницу учебных модулей «Девмана» и выбирайте тему.

Полное руководство по модулю asyncio в Python. Часть 2

Публикуем вторую часть руководства по модулю asyncio в Python, в которой представлены разделы оригинала №3 и 4. Читать головокружительную первую часть.

3. Когда стоит использовать модуль asyncio

Если описать модуль asyncio несколькими словами, то получится, что это — нечто новое, популярное, широко обсуждаемое и интересное.

Но, несмотря на это, вопрос о том, когда в некий проект стоит внедрить asyncio , это — вопрос спорный и неоднозначный.

Когда же asyncio стоит использовать в Python-проектах?

3.1. Причины использования asyncio в Python-проектах

Вероятно, можно назвать три основные причины использования asyncio в Python-проектах:

  1. Чтобы применять в программе корутины.
  2. Чтобы воспользоваться парадигмой асинхронного программирования.
  3. Чтобы использовать неблокирующую обработку ввода/вывода.

Рассмотрим каждую из этих причин подробнее

Причина 1: корутины

Решение о необходимости использования asyncio в некоем проекте может быть принято в ситуации, когда в этом проекте нужны корутины.

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

Корутины — это один из элементов конкурентного программирования, наподобие потоков или процессов.

Конкурентность, основанная на потоках, доступна Python-программистам благодаря модулю threading , она поддерживается на уровне операционной системы, в которой работает Python-программа. Такая конкурентность подходит для обработки блокирующих операций ввода/вывода, таких, как запись и чтение данных при работе с файлами, сокетами, устройствами.

Конкурентность, основанная на процессах, реализуется на основе механизмов из модуля multiprocessing . Её, как и в случае с потоками, поддерживает операционная система. Она подходит для решения задач, скорость выполнения которых привязана к производительности CPU, но которые при этом не предусматривают интенсивного межпроцессного взаимодействия. Это, например, тяжёлые вычислительные задачи.

Корутины — это альтернатива потокам и процессам. Программисту их предоставляет сам язык Python и среда его выполнения (стандартный интерпретатор), а их поддержка основана на модуле asyncio . Они подходят для обработки неблокируюих операций ввода/вывода с использованием подпроцессов и сокетов. Впрочем, заниматься блокирующим вводом/выводом и решать тяжёлые вычислительные задачи можно, используя подход, имитирующий решение неблокирующих задач, когда в недрах программы всё сводится к применению потоков и процессов.

Последняя мысль — это очень тонкий и очень важный момент. Хотя корутины можно использовать, ориентируясь на те возможности, ради которых они и появились в Python, то есть — для выполнения неблокирующих операций, их, на самом деле, можно использовать для решения любых задач. Любую программу, написанную с использованием потоков или процессов, можно переписать с применением корутин. Или, если возникнет такое желание, то, что раньше писали с использованием потоков и процессов, теперь можно писать, применяя корутины.

Потоки и процессы позволяют достичь многозадачности через механизмы операционной системы, которая решает — какой поток или процесс надо запустить, когда его надо запустить, сколько процессорного времени ему дать. Операционная система быстро переключается между потоками и процессами, приостанавливая те, которые отработали отведённое им время, и возобновляя выполнение тех, которым выделено время на работу. Это называют вытесняющей многозадачностью.

А корутины в Python реализуют альтернативный подход к многозадачности — кооперативную многозадачность.

Корутины — это подпрограммы (функции), выполнение которых можно приостанавливать и возобновлять. Их выполнение приостанавливается с помощью выражения await , а возобновляется после того, как это выражение разрешится.

Корутины задуманы именно такими, это позволяет им кооперироваться, решая — когда и где приостановить выполнение.

Это — альтернативный, интересный и мощный подход к конкурентности, отличающийся от подхода, основанного на потоках и процессах.

Одного этого может быть достаточно для того чтобы принять решение о внедрении в проект модуля asyncio .

Ещё одна важнейшая особенность корутин заключается в том, что они легковесны.

Они «легче» потоков. То есть — быстрее запускаются и используют меньше памяти. В сущности, корутины — это особая разновидность функций, а вот потоки представлены Python-объектами и связаны с потоками в операционной системе, с которыми эти объекты должны взаимодействовать.

В силу этого в Python-программе могут использоваться тысячи потоков. А если говорить о корутинах, то их уже может быть десятки или сотни тысяч, все они будут размещены в одном потоке.

Программист может решить применять корутины из-за их масштабируемости.

Причина 2: асинхронное программирование

Решение о том, что в некоем проекте нужен модуль asyncio , можно принять, руководствуясь необходимостью применения в нём методов асинхронного программирования.

То есть — когда нужно разработать Python-программу, в которой применяется парадигма асинхронного программирования.

«Асинхронный», как мы уже говорили — это значит «не одновременный во времени», в противоположность «синхронному» — чему-то такому, что происходит в одно и то же время.

В программировании «асинхронность» означает, что выполнение некоего действия запрашивается, но во время запроса это действие не выполняется. Оно выполняется позже.

Под асинхронным программированием часто понимают проектирование программы вокруг концепции асинхронных вызовов функций и задач.

Хотя существуют и другие способы реализации элементов асинхронного программирования в Python, полноценное асинхронное программирование в Python требует использования корутин и модуля asyncio .

Это — Python-библиотека, которая позволяет нам выполнять код, используя модель асинхронного программирования.

Python Concurrency with asyncio, 2022, с. 3

Решить использовать asyncio можно из-за того, что в программе нужно применять модуль асинхронного программирования, и это — оправданная причина.

Выражусь предельно ясно: эта причина независима от использования неблокирующего ввода/вывода. Асинхронным программированием можно пользоваться независимо от неблокирующей обработки ввода/вывода.

Как мы уже видели, корутины могут выполнять неблокирующие операции ввода/вывода асинхронно. Но модуль asyncio , кроме того, даёт нам возможность выполнять в асинхронном стиле блокирующие операции ввода/вывода и задачи, скорость выполнения которых привязана к производительности CPU. Делается это путём имитации неблокирующих механизмов в недрах системы посредством потоков и процессов.

Причина 3: неблокирующий ввод/вывод

Принять решение о необходимости использования модуля asyncio можно и из-за того, что разработчику хочется или нужно применять неблокирующий ввод/вывод.

Ввод/вывод (Input/Output, I/O) — это чтение или запись данных при работе с неким ресурсом.

Среди распространённых примеров таких ресурсов можно отметить следующие:

  • Жёсткие диски: операции с файлами — чтение и запись данных, присоединение данных к файлу, переименование, удаление файлов и другие операции.
  • Периферийные устройства: мышь, клавиатура, дисплей, принтер, последовательный порт, камера и так далее.
  • Интернет: загрузка и выгрузка файлов, получение данных веб-страниц, выполнение запросов к RSS-ресурсам и так далее.
  • Базы данных: выборка, обновление, удаление информации посредством SQL запросов и выполнение других подобных операций.
  • Электронная почта: отправка и получение почты, обращение к почтовому ящику и другие операции.

Эти операции медленны в сравнении со скоростью, с которой выполняются вычисления в процессоре.

Распространённый подход к реализации подобных операций в программах заключается в выполнении запросов на чтение или на запись и в ожидании отправки или получения данных.

В результате эти операции часто называют блокирующими задачами ввода/вывода.

Операционная система, видя, что поток, вызывающий подобные операции, заблокирован, выполняет переключение контекста на другой поток, который способен воспользоваться ресурсами процессора.

Это ведёт к тому, что блокирующие вызовы не замедляют всю систему. Но такие операции останавливают или блокируют потоки или программы, выполняющие блокирующие вызовы.

Подробнее о блокирующих вызовах можно почитать в этом руководстве.

Неблокирующий ввод/вывод — это альтернатива блокирующему вводу/выводу.

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

Неблокирующий ввод/вывод позволяет выполнять вызов операций чтения и записи данных в виде асинхронных запросов.

Операционная система обрабатывает такие запросы и уведомляет вызывающую программу тогда, когда будут доступны их результаты.

Неблокирующий ввод/вывод: выполнение операций ввода/вывода посредством асинхронных запросов и ответов вместо выполнения их так, что приходится ждать завершения операции.

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

Комбинация неблокирующего ввода/вывода и асинхронного программирования так распространена, что её обычно сокращённо называют асинхронным вводом/выводом.

Асинхронный ввод/вывод: условное сокращение, которое означает комбинацию асинхронного программирование и неблокирующей обработки ввода/вывода.

Модуль asyncio был добавлен в Python, в частности, для того, чтобы оснастить стандартную библиотеку языка поддержкой неблокирующего ввода/вывода с использованием подпроцессов (например — выполнение команд в операционной системе) и с использованием потоков (например — программирование TCP-сокетов).

Можно сымитировать неблокирующую обработку ввода/вывода с использованием потоков и возможностей асинхронного программирования, предоставляемых пулами потоков Python или исполнителями пулов потоков.

Модуль asyncio даёт нам стандартную поддержку асинхронного программирования для организации неблокирующего ввода/вывода посредством корутин, циклов событий и объектов, представляющих неблокирующие подпроцессы и потоки.

Решить использовать asyncio можно из-за желания применения асинхронного ввода/вывода в своих программах. И это — достойный повод для принятия такого решения.

3.2. Другие причины использования asyncio

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

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

Вот ещё несколько причин использования asyncio в некоем проекте:

  • Кто-то другой принял такое решение.
  • В проекте, над которым вы работаете, asyncio уже используется.
  • Разработчик, решив использовать asyncio , хочет лучше изучить этот модуль.

Программисты не всегда обладают полным контролем над проектами, над которыми они работают.

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

Одной из таких технологий может быть asyncio .

Использование asyncio при работе над существующим проектом может быть объяснено тем, что этот модуль в нём уже используется. То есть, речь уже не идёт о выборе — программист вынужден использовать то, что уже применяется в проекте.

Похожим примером может быть случай, когда программист планирует внедрить в проект некий код, решающий его задачу, в котором уже используется asyncio .

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

Если у вас нет альтернатив тому решению, где используется asyncio , применение этого модуля может быть попросту навязано вам в тот момент, когда вы выбираете это решение.

И наконец — решить применять asyncio можно с целью лучше изучить этот модуль.

Кому-то эта причина может показаться несерьёзной: «А как же требования к проекту?».

Дело в том, что если некто решит внедрить в проект asyncio просто потому, что ему хочется опробовать этот модуль, то это — вполне достойная причина так поступить.

В таком случае использование asyncio в проекте будет иметь смысл для конкретного программиста.

3.3. Когда не стоит использовать asyncio

Мы потратили немало времени, говоря о том, когда стоит использовать asyncio .

Вероятно, правильно будет уделить хотя бы несколько минут на то, чтобы обсудить случаи, когда использовать asyncio не стоит.

Одна из причин не использовать asyncio заключается в том, что тот, кто подумывает об этом, не может опереться ни на один из вышеприведённых доводов за asyncio .

Такой подход, правда, может приводить к принятию ошибочных решений. Дело в том, что asyncio может понадобиться в проекте и по причинам, о которых мы не говорили.

Предположим, вы вооружились некоей идеей, говорящей о необходимости asyncio в вашем проекте. Если эта идея выглядит недостаточно веской, если в ней есть изъяны, то, возможно, asyncio — это не то, что вам нужно.

Полагаю, что главная причина неиспользования asyncio в проекте заключается в том, что этот модуль не даёт тех плюсов, которые, как считает разработчик, он должен давать проекту.

Вокруг конкурентного выполнения код в Python, а особенно — вокруг asyncio , существует множество заблуждений.

Например — вот что можно слышать о модуле asyncio :

  • Он способен решить проблему глобальной блокировки интерпретатора (Global Interpreter Lock, GIL).
  • Он быстрее, чем потоки.
  • Он избавляет разработчика от необходимости использовать мьютексы и другие примитивы синхронизации.
  • Им легче пользоваться, чем потоками.

Всё это — неправда.

В некий момент времени может выполняться лишь код одной корутины — так уж они устроены. Они, при выполнении, кооперируются. Это очень похоже на то, как работают потоки под GIL. На самом деле, GIL к нашему делу не относится. Вероятно, в большинстве ситуаций, когда используется asyncio , разговоры о GIL не играют особой роли.

Любую программу, которую можно написать, пользуясь asyncio , можно написать и с помощью потоков. Она, вероятно, будет столь же быстрой, или быстрее, чем её asyncio-версия. Ещё она, скорее всего, окажется проще, а другим разработчикам будет проще читать и понимать её код.

Любая ошибка, связанная с конкурентным выполнением кода, которую можно ожидать при использовании потоков, может встретиться и при работе с корутинами. Корутины, как и потоки, нужно защищать от взаимных блокировок и от состояний гонок.

Ещё одной причиной отказа от asyncio может являться то, что программисту не нравится асинхронное программирование.

Асинхронное программирование пользуется популярностью уже немалое время, к нему хорошо относятся программисты, пишущие на множестве языков, прежде всего — на JavaScript.

Асинхронное программирование отличается от процедурного, объектно-ориентированного, функционального программирования. Некоторым программистам оно просто не нравится.

Если в вашем случае так оно и есть — не проблема. Не нравится — не пользуйтесь. Это — обоснованная причина отказа от asyncio .

Того же результата, что достижим с помощью asyncio , можно добиться и другими способами. В частности — путём выполнения нескольких асинхронных запросов, применяя, при необходимости, исполнители процессов или потоков.

Теперь, когда мы поговорили о том, когда asyncio использовать не стоит, поближе познакомимся с корутинами.

4. Корутины в Python

В Python имеется встроенная поддержка типа coroutine и новых выражений — наподобие async def и await .

Модуль asyncio в Python позволяет запускать корутины и разрабатывать асинхронные программы.

В этом разделе мы гораздо ближе, чем прежде, познакомимся с корутинами.

4.1. Что такое корутина

Корутина (coroutine, сопрограмма) — это функция, выполнение которой может быть приостановлено и возобновлено.

Корутины часто называют более общими формами подпрограмм.

Подпрограмму можно запустить, её работа начнётся в одном месте и завершится в другом. А корутину можно запустить, потом — приостановить, потом — возобновить. Приостанавливать и возобновлять выполнение корутины можно много раз — до того момента, пока её работа не будет, в итоге, завершена.

В частности, корутины могут управлять тем, где именно они приостанавливают своё выполнение.

Для этого может привлекаться специфическое выражение — такое, как await , похожее на выражение yield , применяемое в Python-генераторах.

Корутина — это метод, который можно приостановить, когда у нас имеется задача, выполнение которой может занять много времени. Потом, когда эта задача будет завершена, метод можно возобновить. В Python 3.5. была реализована встроенная поддержка корутин и асинхронного программирования, тогда в язык были добавлены ключевые слова async и await.

Python Concurrency with asyncio, 2022, с. 3

Выполнение корутины может приостанавливаться по многим причинам. Среди них — выполнение другой корутины, например — ожидание завершения некоей задачи. Это может быть ожидание возврата данных процессом или неким внешним ресурсом, таким — как соединение между сокетами.

Корутины используются для организации конкурентного выполнения кода.

Корутины позволяют иметь в Python-программах большое количество функций, которые, с виду, выполняются одновременно.

Effective Python, 2019, с. 267

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

Это называют кооперативной многозадачностью. Такой подход к многозадачности отличается от вытесняющей многозадачности, обычно используемой при работе с потоками.

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

Cooperative multitasking, Wikipedia

Реализация вытесняющей многозадачности предусматривает участие операционной системы в принятии решений о том, какой поток и когда нужно приостановить или возобновить. Это отличается от подхода, применяемого в кооперативной многозадачности, когда задачи сами принимают подобные решения.

Теперь, когда у нас есть некоторое представление о том, что такое корутины, углубим наши знания, сравнив их с другими широко известными концепциями программирования.

4.2. Сравнение корутин с программами и подпрограммами

Программа (routine) и подпрограмма (subroutine) — это понятия, которые в современном программировании часто используют для обозначения одних и тех же сущностей.

Но, возможно, правильнее будет говорить о том, что «программа» — это самостоятельная программа, а «подпрограмма» — это функция в «программе».

Получается, что у программ есть подпрограммы.

Подпрограмма — это отдельный модуль, содержащий набор выражений, которому назначено имя. Она может принимать аргументы и возвращать значения.

Подпрограмма: модуль с набором инструкций, который может быть выполнен по запросу. Обычно ей назначается имя, она способна принимать аргументы и возвращать значения. Подпрограммы ещё называют функциями.

Подпрограмма запускается, выполняет содержащиеся в ней выражения и каким-то образом завершает работу, возвращая управление в место своего вызова. Обычно подпрограммы вызываются другими подпрограммами.

А корутина, или сопрограмма — это расширенный вариант подпрограммы. То есть — подпрограммы можно считать особым типом корутин.

Корутины во многом похожи на подпрограммы. Например:

  • И те и другие — это отдельные именованные модули, содержащие выражения.
  • И те и другие могут принимать аргументы, а могут и не принимать.
  • И те и другие могут возвращать значения или их не возвращать.

Главное их различие заключается в том, что корутины могут самостоятельно приостанавливать и возобновлять своё выполнение много раз до возврата значения и выхода из них.

И корутины, и подпрограммы могут вызывать другие сущности, подобные себе. Подпрограмма может вызывать другие подпрограммы. Корутина способна вызывать другие корутины. Но корутина может ещё и вызывать другие подпрограммы.

Когда корутина вызывает другую корутину — она должна приостановить собственное выполнение и позволить другой корутине возобновить свою работу после того, как работа другой корутины завершится.

Это похоже на то, как одна подпрограмма вызывает другую подпрограмму. Разница в том, что приостановка корутины может, кроме того, позволить выполниться любому количеству других корутин.

Это делает механизм вызова одной корутиной другой корутины более мощным, чем механизм вызова одной подпрограммой другой подпрограммы. Этот механизм занимает центральное место в кооперативной многозадачности, построенной с использованием корутин.

4.3. Сравнение корутин и генераторов

Генератор — это особая функция, которая может приостанавливать своё выполнение.

Генератор: функция, которая возвращает итерируемый генератор. Он выглядит как обычная функция, за исключением того, что содержит выражение yield, с помощью которого выдаются последовательности значений. Их можно применять в циклах for или получать по одному, пользуясь функцией next().

Python glossary

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

Функция-генератор возвращает объект итерируемого генератора, который можно обходить — например — с помощью цикла for . Каждый раз, когда запускается генератор, он выполняется с последнего места, где был приостановлен, до следующего выражения yield .

Итерируемый генератор: объект, созданный функцией-генератором. Каждое выражение yield временно приостанавливает его работу, при этом запоминается состояние выполнения в том месте, где была приостановлена работа (включая локальные переменные и ожидающие разрешения выражения try). Когда итерируемый генератор возобновляет работу — он начинает с того места, где остановился (в отличие от функций, которые, при каждом вызове, начинают работать заново).

Python glossary

Корутина может приостанавливать работу или передавать управление другой корутине, используя выражение await . После завершения работы вызванной корутины вызвавшая её корутина продолжит работу с того места, где была приостановлена.

Используя эту парадигму — выражение await похоже в функции на выражение yield; выполнение текущей функции приостанавливается, а в это время выполняется другой код. После того, как выражение await или yield разрешается данными — выполнение функции возобновляется.

High Performance Python, 2020, с. 218

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

Генераторы, ещё известные как полукорутины, это — подмножество корутин.

Coroutine, Wikipedia

До того, как были созданы корутины, возможности генераторов были расширены до такого уровня, что они могли использоваться в Python-программах подобно корутинам.

Это требовало серьёзных технических знаний о генераторах и о разработке собственных планировщиков задач.

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

Python Cookbook, 2013, с. 524

Это стало возможным благодаря изменениям, внесённым в генераторы, и введению в Python выражения yield from .

Позже эти нововведения были признаны устаревшими ради содействия более современным выражениям async/await .

4.4. Сравнение корутин и задач

Подпрограммы и корутины могут представлять в программах «задачи».

Но в Python есть особый объект, представляющий задачу. Это — объект asyncio.Task.

Объект, похожий на объект Future, который отвечает за выполнение Python-корутин. […] Задачи используются для выполнения корутин в цикле событий.

Asyncio Task Object

Корутину можно обернуть в объект asyncio.Task и выполнить независимо — в противовес её выполнению напрямую, внутри другой корутины. Объект Task даёт средства для асинхронного выполнения корутин.

Задача: обёрнутая корутина, которая может быть выполнена независимо.

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

Объект Task не может существовать сам по себе. В него обязательно должна быть обёрнута корутина.

В результате получается, что объект Task , представляющий задачу — это корутина, но корутина — это не задача.

4.5. Сравнение корутин и потоков

Корутины «легче» потоков.

Корутина объявляется в виде функции.

Поток — это объект, который создаёт и которым управляет операционная система. Он представлен в Python объектом threading.Thread .

Поток: им управляет операционная система, он представлен Python-объектом.

Получается, что обычно корутины создаются и начинают выполняться быстрее потоков, и что они занимают меньше памяти. И наоборот — потоки создаются и запускаются медленнее корутин и требуют больше системных ресурсов.

Цена запуска корутины — это цена вызова функции. После того, как корутина активируется, она использует меньше 1 Кб памяти до тех пор, пока не отработает.

Effective Python, 2019, с. 267

Корутины выполняются в пределах одного потока, в результате один поток может выполнять код множества корутин.

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

Effective Python, 2019, с. 267

Подробнее о потоках можно почитать здесь.

4.6. Сравнение корутин и процессов

Корутины «легче» процессов. На самом же деле — даже потоки «легче» процессов».

Процесс — это компьютерная программа. У неё может быть один или большее количество потоков.

Python-процесс — это отдельный экземпляр Python-интерпретатора.

Процессы, как и потоки, создаёт операционная система, она же ими управляет. Они представлены объектом multiprocessing.Process .

Процесс: им управляет операционная система, он представлен Python-объектом.

Это значит, что корутины создаются и запускаются гораздо быстрее процессов, и то, что они требуют гораздо меньше памяти.

Корутина — это всего лишь особая функция, а процесс — это экземпляр интерпретатора, в котором имеется по меньшей мере один поток.

Если вас интересуют подробности о Python-процессах — загляните сюда.

4.7. Когда корутины были добавлены в Python

Корутины в Python расширили возможности генераторов.

Генераторы медленно и долго двигались к тому, чтобы превратиться в стандартные Python-корутины.

Мы можем исследовать некоторые основные изменения, внесённые в Python ради добавления в язык корутин. Эти изменения мы можем рассматривать как предвестники добавления в язык asyncio .

В генераторы были добавлены новые методы, вроде send() и close() , что позволило приблизить их поведение к поведению корутин.

Они появились в Python 2.5 и описаны в PEP 342.

Это PEP предлагает некоторые расширения API и синтаксиса генераторов, чтобы их можно было использовать как простые корутины.

PEP 342 – Coroutines via Enhanced Generators

Позже генераторам позволили выдавать исключение SuspendIteration , а так же — исключение StopIteration , описанные в PEP 334.

Это PEP предлагает ограниченный подход к созданию корутин, основанный на исключениях протокола итератора. В настоящее время итератор может возбудить исключение StopIteration для указания на то, что он завершил выдавать значения. Это предложение добавляет к данному протоколу ещё одно исключение, SuspendIteration, которое указывает на то, что у некоего итератора могут быть ещё значения, но он в настоящий момент не способен выдать одно из таких значений.

PEP 334 – Simple Coroutines via SuspendIteration

Подавляющее большинство возможностей работы с современными корутинами посредством модуля asyncio было описано в PEP 3156, добавленное в Python 3.3.

Это — предложение по реализации асинхронного ввода/вывода в Python 3, начиная с Python 3.3. Считайте это конкретным предложением возможностей, которых не хватает в PEP 3153. Это предложение включает в себя описание подключаемого цикла событий, абстракций транспортного уровня и уровня протоколов, похожих на те, что имеются в Twisted, описание высокоуровневого планировщика, основанного на yield from (PEP 380). Предлагаемое имя пакета — asyncio.

PEP 3156 – Asynchronous IO Support Rebooted: the «asyncio» Module

Второй подход к работе с корутинами, основанный на генераторах, был добавлен в Python 3.4 в виде расширения Python-генераторов.

Корутины были определены как функции, использующие декоратор @asyncio.coroutine .

Корутины исполнялись с использованием цикла событий asyncio , посредством модуля asyncio .

Корутина могла приостанавливаться и выполнять другую корутину, используя выражение yield from .

# объявление собственной корутины в Python 3.4 @asyncio.coroutine def custom_coro(): # приостановка и выполнение другой корутины yield from asyncio.sleep(1)

Выражение yield from было определено в PEP 380.

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

PEP 380 – Syntax for Delegating to a Subgenerator

Выражением yield from всё ещё можно пользоваться в генераторах, хотя этот подход признан устаревшим для приостановки выполнения корутин. Вместо него рекомендуется использовать выражение await.

Обратите внимание: поддержка корутин, основанных на генераторах, признана устаревшей и удаляется из Python 3.11. Корутины, основанные на генераторах, появились до синтаксической конструкции async/await. Это — Python-генераторы, которые используют выражения yield from для ожидания на объектах Future и на других корутинах.

Asyncio Coroutines and Tasks

Мы можем сказать, что корутины были добавлены в Python в виде стандартных возможностей языка в версии 3.5.

Сюда входят изменения, внесённые в язык, такие, как выражения async def , await , async with и async for , а так же — тип coroutine .

Эти изменения были описаны в PEP 492.

Это предложение заключается в том, чтобы сделать корутины настоящим самостоятельным понятием в Python и ввести новый синтаксис для их поддержки. Конечная цель предложения заключается в том, чтобы способствовать созданию единой, легко доступной ментальной модели асинхронного программирования в Python, и чтобы сделать эту модель как можно ближе к модели синхронного программирования.

PEP 492 – Coroutines with async and await syntax

Теперь, когда мы знаем о том, что такое корутины, пришло время разобраться с тем, как ими пользоваться.

О, а приходите к нам работать? �� ��

Мы в wunderfund.io занимаемся высокочастотной алготорговлей с 2014 года. Высокочастотная торговля — это непрерывное соревнование лучших программистов и математиков всего мира. Присоединившись к нам, вы станете частью этой увлекательной схватки.

Мы предлагаем интересные и сложные задачи по анализу данных и low latency разработке для увлеченных исследователей и программистов. Гибкий график и никакой бюрократии, решения быстро принимаются и воплощаются в жизнь.

Сейчас мы ищем плюсовиков, питонистов, дата-инженеров и мл-рисерчеров.

Короутины (coroutine)

Вопросы, касающиеся короутин (coroutine) и asyncio, относятся к этому разделу.

Это содержание было взято непосредственно из документации и унаследованно от discord.py . Скорее всего, в будущем оно будет переписано.

Что такое coroutine?​

Сoroutine это функция, которая должна быть вызвана с помощью await или yield from . Когда Python сталкивается с await , он останавливает выполнение функции в этот момент и работает над другими вещами, пока не вернется к этой точке и не завершит свою работу. Это позволяет вашей программе выполнять несколько задач одновременно без использования потоков или сложной многопроцессорной обработки.

Где я могу использовать await ?​

Вы можете использовать await только в async def функциях и нигде больше.

Что означает «blocking»?​

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

Если ведение журнала включено, эта библиотека попытается предупредить вас о том, что происходит блокировка, с сообщением: Heartbeat blocked for more than N seconds. См. Раздел Настройка Ведения Журнала для получения подробной информации о включении ведения журнала.

Распространенным источником слишком длительной блокировки является что-то вроде time.sleep . Не делай этого. Используйте asyncio.sleep вместо этого. Аналогично этому примеру:

# Плохо time.sleep(10)  # Хорошо await asyncio.sleep(10) 

Другим распространенным источником слишком длительной блокировки является использование HTTP-запросов с известным модулем Requests: HTTP for Humans™. В то время как Requests: HTTP for Humans™ — это удивительный модуль для неасинхронного программирования, он не является хорошим выбором для asyncio , потому что некоторые запросы могут блокировать цикл событий слишком долго. Вместо этого используйте библиотеку aiohttp , которая уже установлена с disnake.

Рассмотрим следующий пример:

# Плохо r = requests.get("http://aws.random.cat/meow") if r.status_code == 200:  json = r.json() await channel.send(json["file"])  # Хорошо async with aiohttp.ClientSession() as session: async with session.get("http://aws.random.cat/meow") as r: if r.status == 200:  json = await r.json() await channel.send(json["file"]) 

Корутины и задачи¶

В этом разделе приведено высокоуровневое API asyncio для работы с корутинами и задачами.

  • Корутины
  • Ожидаемые объекты
  • Запуск asyncio программы
  • Создание задач
  • Сон
  • Конкурентный запуск задач
  • Защита от отмены
  • Таймауты
  • Примитивы ожидания
  • Планирование из других потоков
  • Интроспекция
  • Объект задачи
  • Основанные на генераторах корутины

Корутины¶

Корутины , объявляемые с помощью async/await синтаксиса, является предпочтительным способом написания asyncio приложений. Например, следующий фрагмент кода (требует Python 3.7) напечатает «hello», ожидает 1 секунду, а затем печатает «world»:

>>> import asyncio >>> async def main(): . print('hello') . await asyncio.sleep(1) . print('world') >>> asyncio.run(main()) hello world 

Заметим, что простой вызов корутины не приведёт к её выполнению:

>>> main()

Чтобы фактически запустить корутину, asyncio предоставляет три основных механизма:

  • Функция asyncio.run() для запуска функции точки входа верхнего уровня «main()» (см. приведённый пример выше)
  • Ожидающая корутина. Следующий фрагмент кода напечатает «hello» после ожидания в 1 секунду, а затем напечатает «world» после ожидания в течении ещё 2х секунд

import asyncio import time async def say_after(delay, what): await asyncio.sleep(delay) print(what) async def main(): print(f"started at time.strftime('%X')>") await say_after(1, 'hello') await say_after(2, 'world') print(f"finished at time.strftime('%X')>") asyncio.run(main()) 

Ожидаемый вывод:

started at 17:13:52 hello world finished at 17:13:55 
async def main(): task1 = asyncio.create_task( say_after(1, 'hello')) task2 = asyncio.create_task( say_after(2, 'world')) print(f"started at time.strftime('%X')>") # Подождать, пока обе задачи не будут выполнены (должны принять # около 2 секунд.) await task1 await task2 print(f"finished at time.strftime('%X')>") 

Обратите внимание, что ожидаемые выходные данные теперь показывают, что фрагмент выполняется на 1 секунду быстрее, чем ранее:

started at 17:14:32 hello world finished at 17:14:34 

Ожидаемые объекты¶

Мы говорим, что объект является ожидаемым объектом, если его можно использовать в await выражении. Многие API-интерфейсы asyncio предназначены для приёма ожидаемых. Существует три основных типа ожидаемых объектов: корутины, задачи и футуры.

Python корутины являются ожидаемыми и поэтому могут ожидаться из других корутин:

import asyncio async def nested(): return 42 async def main(): # Ничего не произойдет, если мы просто вызовем "nested()". # Объект корутины создан, но не await, # так что *не будет работать вообще*. nested() # Давайте сделаем это по-другому и подождём: print(await nested()) # Напечатает "42". asyncio.run(main()) 

В этой документации термин «корутина» может использоваться для двух тесно связанных понятий:

  • Функция корутина: функция async def ;
  • Объект корутины: возвращенный объект после вызова функции корутины.

asyncio также поддерживает устаревшие основанные на генераторах корутины.

Задачи используются для конкурентного планирования корутин.

Когда корутина обвёрнута в Задачу с такими функциями, как asyncio.create_task() , то автоматически планируется запуск корутины в ближайшее время:

import asyncio async def nested(): return 42 async def main(): # Запланировать nested() ближайший одновременный запуск # с "main()". task = asyncio.create_task(nested()) # "task" теперь может использовать отмену "nested()" или # можно просто ждать, пока она не завершится: await task asyncio.run(main()) 

Объект Future — это специальный ожидаемый (await) объект низкого уровня , представляющий конечный результат асинхронной операции.

Когда объект Футуры ожидается, это означает, что корутина будет ждать, пока Футура будет решена в каком-то другом месте.

Объекты Футуры в asyncio нужны, чтобы позволить основанному на колбэках коду использоваться с async/await.

Обычно нет нужды создавать объекты Футуры на уровне кода приложения.

Объекты Футуры, иногда раскрываемые библиотеками и некоторыми asyncio API, могут быть ожидаемыми:

async def main(): await function_that_returns_a_future_object() # это также верно: await asyncio.gather( function_that_returns_a_future_object(), some_python_coroutine() ) 

Хорошим примером низкоуровневой функции, возвращающей объект Футуры, является loop.run_in_executor() .

Запуск asyncio программы¶

asyncio. run ( coro, *, debug=False ) ¶

Выполняет корутину coro и возвращает результат.

Функция управляет переданной корутиной, заботясь об управлении asyncio событийного цикла и завершения асинхронных генераторов.

Функция не может быть вызвана, когда в том же потоке выполняется другой asyncio событийный цикл.

Если debug — True , событийный цикл будет выполняться в режиме отладки.

Функция всегда создаёт новый событийный цикл и закрывает его в конце. Его следует использовать в качестве основной точки входа для asyncio программ, и в идеале его следует вызывать только один раз.

async def main(): await asyncio.sleep(1) print('hello') asyncio.run(main()) 

Добавлено в версии 3.7.

Исходный код asyncio.run() можно найти в Lib/asyncio/runners.py.

Создание задач¶

asyncio. create_task ( coro, *, name=None ) ¶

Обёртывание coro корутины в Task и запланировать её выполнение. Возвращает объект задачи.

Если name не None , он задаётся как имя задачи с помощью Task.set_name() .

Задача выполняется в цикле, возвращенного get_running_loop() . Вызывает RuntimeError , если в текущем потоке нет запущенного цикла.

Функция была добавлена в Python 3.7. Ранее Python 3.7 вместо неё можно использовать низкоуровневую функцию asyncio.ensure_future() :

async def coro(): . # В Python 3.7+ task = asyncio.create_task(coro()) . # Это работает во всех версиях Python, но менее читабельно task = asyncio.ensure_future(coro()) . 

Добавлено в версии 3.7.

Изменено в версии 3.8: Добавлен параметр name .

Сон¶

coroutine asyncio. sleep ( delay, result=None, *, loop=None ) ¶

Блокировка на delay секунд.

Если result предоставляется, он возвращается вызывающему после завершения корутины.

sleep() всегда приостанавливает выполнение текущей задачи, позволяя выполнять другие задачи.

Устарело с версии 3.8, будет удалено в 3.10 версии.: Параметр loop.

Пример корутины, отображающей текущую дату каждую секунду в течение 5 секунд:

import asyncio import datetime async def display_date(): loop = asyncio.get_running_loop() end_time = loop.time() + 5.0 while True: print(datetime.datetime.now()) if (loop.time() + 1.0) >= end_time: break await asyncio.sleep(1) asyncio.run(display_date()) 

Конкурентный запуск задач¶

awaitable asyncio. gather ( *aws, loop=None, return_exceptions=False ) ¶

Запускает ожидаемые объекты в последовательности aws конкурентно.

Если какой-либо ожидаемый объект в aws является корутиной, он автоматически назначается как задача.

Если все await объекты выполнены успешно, результатом является сводный список возвращенных значений. Порядок значений результата соответствует порядку await в aws.

Если return_exceptions является False (по умолчанию), первое вызванное исключение немедленно распространяется на задачу, которая ожидает на gather() . Другие await объекты в aws последовательности не будут отменены и продолжат работу.

При return_exceptions True исключения обрабатываются так же, как и успешные результаты, и агрегируются в списке результатов.

Если gather() отменён, все представленные ожидаемые (которые ещё не завершены) также будут отменены.

Если какая-либо задача или футура в последовательности aws отменена, это рассматривается, как будто сработало исключение CancelledError — вызов gather() не отменяется в этом случае. Это необходимо для предотвращения отмены одной отправленной задачи/футуры, чтобы привести к отмене других задач/футур.

Устарело с версии 3.8, будет удалено в 3.10 версии.: Параметр loop.

import asyncio async def factorial(name, number): f = 1 for i in range(2, number + 1): print(f"Task name>: Compute factorial(i>). ") await asyncio.sleep(1) f *= i print(f"Task name>: factorial(number>) = f>") async def main(): # Запланировать дерево вызовов *конкурентно*: await asyncio.gather( factorial("A", 2), factorial("B", 3), factorial("C", 4), ) asyncio.run(main()) # Ожидаемый вывод: # # Task A: Compute factorial(2). # Task B: Compute factorial(2). # Task C: Compute factorial(2). # Task A: factorial(2) = 2 # Task B: Compute factorial(3). # Task C: Compute factorial(3). # Task B: factorial(3) = 6 # Task C: Compute factorial(4). # Task C: factorial(4) = 24 

Если return_exceptions содержит значение False, отмена gather() после того, как он был помечен как выполненный, не отменит ни одного отправленного ожидаемого объекта. Например, gather может быть помечена как выполненная после передачи исключения вызывающей стороне, поэтому вызов gather.cancel() после перехвата исключения (вызванного одним из ожидаемых объектов) из gather не отменяет другие ожидаемые объекты.

Изменено в версии 3.7: Если gather отменяется, отмена распространяется независимо от return_exceptions.

Защита от отмены¶

awaitable asyncio. shield ( aw, *, loop=None ) ¶

Если aw корутина, она автоматически назначается как задача.

res = await shield(something()) 
res = await something() 

кроме того, что если корутина, содержащая её, отменяется, задача, выполняемая в something() , не отменяется. С точки зрения something() отмены не произошло. Хотя его вызывающий объект всё ещё отменён, «await» выражение по-прежнему вызывает CancelledError .

Если something() отменяется другими средствами (т.е. изнутри), которые также отменяют shield() .

Если требуется полностью игнорировать отмену (не рекомендуется), функция shield() должна быть объединена с предложением try/except следующим образом:

try: res = await shield(something()) except CancelledError: res = None 

Устарело с версии 3.8, будет удалено в 3.10 версии.: Параметр loop.

Таймауты¶

coroutine asyncio. wait_for ( aw, timeout, *, loop=None ) ¶

Дождаться завершения aw ожидаемого с таймаутом.

Если aw корутина, она автоматически назначается как задача.

timeout может быть либо None , либо числом секунд ожидания с плавающей запятой, либо int числом. Если timeout None , блокировать до завершения футуры.

Если завершается таймаут, задача отменяется и вызывается asyncio.TimeoutError .

Чтобы избежать отмены задачи, оберните её в shield() .

Функция будет ждать, пока футура будет фактически отменена, поэтому общее время ожидания может превысить timeout.

Если ожидание отменяется, то также отменяется и будущий aw.

Устарело с версии 3.8, будет удалено в 3.10 версии.: Параметр loop.

async def eternity(): # Спать в течение одного часа await asyncio.sleep(3600) print('yay!') async def main(): # Ожидать не более 1 секунды try: await asyncio.wait_for(eternity(), timeout=1.0) except asyncio.TimeoutError: print('timeout!') asyncio.run(main()) # Ожидаемый вывод: # # timeout! 

Изменено в версии 3.7: Когда aw отменяется из-за тайм-аута, wait_for ожидает отмены aw. Ранее она сразу вызывала asyncio.TimeoutError .

Примитивы ожидания¶

coroutine asyncio. wait ( aws, *, loop=None, timeout=None, return_when=ALL_COMPLETED ) ¶

Конкурентный запуск ожидаемых объектов в итерации aws и блокировка, пока не будет выполнено условие, указанное в return_when.

Возвращает два множества задачи/футуры: (done, pending) .

done, pending = await asyncio.wait(aws) 

timeout (float или int), если он указан, можно использовать для управления максимальным количеством секунд ожидания перед возвращением.

Обратите внимание, что функция не вызывает asyncio.TimeoutError . Футуры или задачи, которые не были выполнены при наступлении тайм-аута, просто возвращаются во втором множестве.

return_when указывает, когда функция должна возвращать. Она должна быть одной из следующих констант:

Константа Описание
FIRST_COMPLETED Функция возвращает, когда любая футура завершится или отменится.
FIRST_EXCEPTION Функция возвращает после завершения любого процесса футуры путём создания исключения. Если в футуре исключение не вызывается, то оно эквивалентно ALL_COMPLETED .
ALL_COMPLETED Функция возвращает после завершения или отмены всех футур.

В отличие от wait_for() , wait() не отменяет футуры при наступлении тайм-аута.

Не рекомендуется, начиная с версии 3.8: Если какой-либо ожидаемый в aws является корутиной, он автоматически назначается как задача. Непосредственная передача объектов корутине в wait() является устаревшей практикой, т. к. приводит к запутанному поведению .

Устарело с версии 3.8, будет удалено в 3.10 версии.: Параметр loop.

wait() автоматическое планирование корутины как задачи, затем возвращает создаваемые объекты задачи в множестве (done, pending) . Поэтому следующий код не будет работать так, как ожидалось:

async def foo(): return 42 coro = foo() done, pending = await asyncio.wait(coro>) if coro in done: # Ветка никогда не будет запущена! 

Вот как можно зафиксировать вышеуказанный фрагмент:

async def foo(): return 42 task = asyncio.create_task(foo()) done, pending = await asyncio.wait(task>) if task in done: # Теперь все будет работать так, как и ожидалось. 

Не рекомендуется, начиная с версии 3.8: Передача корутиновых объектов непосредственно в wait() устарела.

asyncio. as_completed ( aws, *, loop=None, timeout=None ) ¶

Запуск ожидаемых объектов в aws итеративно и конкурентно. Возвращает итератор корутины. Каждую возвращенную корутину можно ожидать, чтобы получить самый ранний следующий результат от итерируемого из оставшихся ожидаемых.

Вызывает asyncio.TimeoutError , если тайм-аут наступает до выполнения всех футур.

Устарело с версии 3.8, будет удалено в 3.10 версии.: Параметр loop.

for coro in as_completed(aws): earliest_result = await coro # . 

Планирование из других потоков¶

asyncio. run_coroutine_threadsafe ( coro, loop ) ¶

Отправить корутину в событийный цикл. Потокобезопасный.

Возвращает concurrent.futures.Future дожидаясь результата из другого потока ОС.

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

# Создание корутины coro = asyncio.sleep(1, result=3) # Отправить корутину в заданный цикл future = asyncio.run_coroutine_threadsafe(coro, loop) # Ожидать результата с необязательным аргументом timeout assert future.result(timeout) == 3 

Если в корутине вызывается исключение, возвращенная футура будет уведомлена. Также можно использовать отмену задачи в событийном цикле:

try: result = future.result(timeout) except asyncio.TimeoutError: print('The coroutine took too long, cancelling the task. ') future.cancel() except Exception as exc: print(f'The coroutine raised an exception: exc!r>') else: print(f'The coroutine returned: result!r>') 

В отличие от других asyncio функций данная функция требует явной передачи loop аргумента.

Добавлено в версии 3.5.1.

Интроспекция¶

asyncio. current_task ( loop=None ) ¶

Возвращает текущую Task сущность или None , если задача не выполняется.

Если loop — None , используется get_running_loop() для получения текущего цикла.

Добавлено в версии 3.7.

asyncio. all_tasks ( loop=None ) ¶

Возвращает множество ещё не завершенных объектов Task , запущенных в цикле.

Если loop — None , используется get_running_loop() для получения текущего цикла.

Добавлено в версии 3.7.

Объект задачи¶

class asyncio. Task ( coro, *, loop=None, name=None ) ¶

Футуроподобный объект, запускающий Python корутину . Не потокобезопасной.

Задачи используются для запуска корутин в событийных циклах. Если корутина ожидает футуры, задача приостанавливает выполнение корутины и ожидает завершения футуры. Когда выполнено, футура возобновляет исполнение обёрнутой корутины.

Событийный цикл использует совместное планирование: событийный цикл выполняет одну задачу одновременно. В то время как задача ожидает для завершения футуры, событийный цикл выполняет другие задачи, колбэки или выполняет операции ввода-вывода.

Используйте высокоуровневую функцию asyncio.create_task() , чтобы создать задачи или низкоуровневые функции loop.create_task() или ensure_future() . Не рекомендуется создавать экземпляры задач вручную.

Для отмены выполняемой задачи используйте метод cancel() . Этот вызов приведёт к тому, что задача бросит CancelledError исключение в обернутую корутину. Если корутина ожидает в объекте футуры во время отмены, объект футуры будет отменён.

Можно использовать cancelled() , чтобы проверить, была ли задача отменена. Метод возвращает True , если обёрнутая корутина не подавила CancelledError исключение и фактически была отменена.

Задачи поддерживают модуль contextvars . При создании задачи она копирует текущий контекст, а затем запускает корутину в скопированном контексте.

Изменено в версии 3.7: Добавлена поддержка модуля contextvars .

Изменено в версии 3.8: Добавлен параметр name .

Устарело с версии 3.8, будет удалено в 3.10 версии.: Параметр loop.

Запрос отмены задачи.

Это позволяет создать CancelledError исключение для обернутой корутины на следующем цикле событийного цикла.

После этого у корутины есть шанс очистить или даже отклонить просьбу, подавив исключение в блоке try … except CancelledError … finally . Поэтому, в отличие от Future.cancel() , Task.cancel() не гарантирует, что задача будет отменена, хотя подавление отмены полностью не распространено и активно отговаривается.

В следующем примере показано, как корутины могут перехватывать запрос на отмену:

async def cancel_me(): print('cancel_me(): before sleep') try: # Ждать 1 секунду await asyncio.sleep(3600) except asyncio.CancelledError: print('cancel_me(): cancel sleep') raise finally: print('cancel_me(): after sleep') async def main(): # Создание задачи "cancel_me" task = asyncio.create_task(cancel_me()) # Ждать 1 секунду await asyncio.sleep(1) task.cancel() try: await task except asyncio.CancelledError: print("main(): cancel_me is cancelled now") asyncio.run(main()) # Ожидаемый результат: # # cancel_me(): before sleep # cancel_me(): cancel sleep # cancel_me(): after sleep # main(): cancel_me is cancelled now 

Возвращает True , если задача отменена.

Задача отменена, когда запрашивалась отмена с cancel() и обернутая корутина распространила в неё CancelledError исключение.

Возвращает True , если задача завершена.

Задача завершена, когда обернутая корутина либо возвратила значение, либо вызвала исключение, либо задача была отменена.

Возвращает результат выполнения задачи.

Если задача является завершенной, то результатом обернутой корутины является возвращаемое (или если корутина вызвала исключение, то это исключение возникает повторно.)

Если задача была отменена, это метод вызывает CancelledError исключение.

Если результат задачи ещё не доступен, это метод вызывает InvalidStateError исключение.

Возвращает исключение задачи.

Если у обернутой корутины возникло исключение, то возвращается это исключение. Если обернутая корутина возвращается нормально, то этот метод возвращает None .

Если задача была отменена, это метод вызывает CancelledError исключение.

Если задача еще не завершена, это метод вызывает InvalidStateError исключение.

add_done_callback ( callback, *, context=None ) ¶

Добавление колбэка для выполнения при выполнении задачи.

Этот метод должен быть использован только в низкоуровневом основанном на колбэках коде.

Для получения дополнительной информации см. документацию Future.add_done_callback() .

Удалить callback из списка колбэков.

Этот метод должен быть использован только в низкоуровневом основанном на колбэках коде.

Для получения дополнительной информации см. документацию Future.remove_done_callback() .

Возвращает список фреймов стека для этой задачи.

Если обёрнутая корутина не выполнена, он возвращает стек, где он приостановлен. Если корутина успешно завершена или отменена, возвращает пустой список. Если корутина была прервана исключением, возвращает список кадров трейсбэка.

Фреймы всегда упорядочиваются от самых старых до самых новых.

Для приостановленной корутины возвращается только одни фрейм стека.

Необязательный аргумент limit устанавливает максимальное количество возвращаемых кадров; по умолчанию возвращаются все доступные фреймы. Порядок возвращаемого списка различается в зависимости от того, возвращается ли стек или трассировка: возвращаются самые новые фреймы стека, но возвращаются самые старые фреймы трассировки. (Это соответствует поведению модуля трассировки.)

Печать стека или трейсбэка для этой задачи.

При этом выводятся выходные данные, аналогичные данным модуля traceback для фреймов, извлекаемых get_stack() .

Аргумент limit передается непосредственно get_stack() .

Аргумент file представляет собой поток I/O, в который записываются выходные данные; по умолчанию выходные данные записываются в sys.stderr .

Возвращает объект корутины, обернутый Task .

Добавлено в версии 3.8.

Возвращает имя задачи.

Если ни одно имя не было явно назначено задаче, реализация задачи asyncio по умолчанию создаёт имя по умолчанию во время создания экземпляра.

Добавлено в версии 3.8.

set_name ( value ) ¶

Задание имя задачи.

Аргументом value может быть любой объект, который затем преобразуется в строку.

В реализации задачи по умолчанию имя будет отображаться в repr() выходных данных объекта задачи.

Добавлено в версии 3.8.

classmethod all_tasks ( loop=None ) ¶

Возвращает множество всех задач для событийного цикла.

По умолчанию возвращаются все задачи для текущего событийного цикла. Если loop None , то используется get_event_loop() функция для получения текущего цикла.

Устарело с версии 3.7, будет удалено в 3.9 версии.: Не вызывайте этот метод задач. Вместо этого используйте функцию asyncio.all_tasks() .

classmethod current_task ( loop=None ) ¶

Возвращает текущую запущенную задачу или None .

Если loop — None , используется функция get_event_loop() для получения текущего цикла.

Устарело с версии 3.7, будет удалено в 3.9 версии.: Не вызывайте этот метод задач. Вместо него используйте функцию asyncio.current_task() .

Основанные на генераторах корутины¶

Поддержка основанных на генераторах корутин запрещено и планируется к удалению в Python 3.10.

Корутины на основе генератора предшествовали синтаксису async/await. Они представляют собой Python генераторы, которые используют yield from выражения для ожидания футур и других корутин.

Генераторные корутины должны быть задекорированы @asyncio.coroutine , хотя это не применяется.

Декоратор для маркировки основанных на генераторах корутин.

Этот декоратор обеспечивает совместимость устаревших основанных на генераторах корутин с async/await кодом:

@asyncio.coroutine def old_style_coroutine(): yield from asyncio.sleep(1) async def main(): await old_style_coroutine() 

Этот декоратор не должен использоваться для async def корутин.

Устарело с версии 3.8, будет удалено в 3.10 версии.: Используйте async def вместо этого.

asyncio. iscoroutine ( obj ) ¶

Метод отличается от inspect.iscoroutine() потому что возвращает True для основанных на генераторах корутин.

asyncio. iscoroutinefunction ( func ) ¶

Метод отличается от inspect.iscoroutinefunction() , потому что возвращает True для основанных на генераторах функций корутин декорированных с @coroutine .

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *