Корутины python что это
Перейти к содержимому

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

  • автор:

Короутины (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"]) 

Корутины в 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

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

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

Пишем нашу первую сопрограмму

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

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

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

Асинхронный код сложный, запутанный, требует много обратных вызовов (callback) и обработки ошибок. Чтобы сделать его сделать более читаемым и понятным используют корутины. Они позволяют писать асинхронный код в синхронном стиле, без необходимости использовать сложные конструкции, такие как промисы (promise) или фьючерсы (future).

Что такое корутины

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

Что могут корутины?

  • Иметь несколько точек входа и выхода: в отличие от обычных подпрограмм, которые имеют одну точку входа и одну точку выхода.
  • Приостанавливать свое выполнение в любой момент: с помощью специального оператора (например, yield в Python или await в Kotlin), сохраняя свое состояние (локальные переменные и стек вызовов).
  • Возобновить свое выполнение с того же места: по запросу другой корутины или внешнего кода.
  • Работать кооперативно: они добровольно отдают управление друг другу, а не конкурируют за ресурсы.
  • Не привязываться к определенному системному потоку (thread), а выполняться поверх них: это означает, что один поток может запускать несколько корутин параллельно, переключаясь между ними по мере необходимости.

Какие языки программирования поддерживают корутины

Корутины не являются новой концепцией в программировании. Они были предложены еще в 1958 году Мелвином Конвеем (Melvin Conway) и использовались в различных языках программирования с тех пор.

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

  • Python имеет ключевые слова async и await для объявления и использования корутин.
  • В Kotlin есть ключевое слово suspend для определения функций, которые могут быть вызваны из корутин.
  • В Lua предусмотрены функции coroutine.create и coroutine.resume для создания и запуска корутин.

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

  • C# имеет класс System.Threading.Tasks.Task для представления асинхронных операций, которые могут быть запущены как корутины.
  • Java имеет библиотеку java.util.concurrent.CompletableFuture для создания и комбинирования асинхронных задач.
  • JavaScript имеет объект Promise для оборачивания асинхронных операций в цепочки обратных вызовов.

Код с обратными вызовами VS с корутинами

Код с обратными вызовами

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

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

Пример кода с callback:

# Асинхронный код с обратными вызовами def load_data (url, callback): # Загрузить данные из url в фоновом потоке # После загрузки вызвать callback c результатом def process_data (data): # Обработать данные def display_data (data): # Отобразить данные на экране load_data("https://example.com ", lambda data: process_data(data)) display_data(data) 

Код с корутинами

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

# Асинхронный код с корутинами async def load_data(url): # Загрузить данные из url в фоновом потоке # Приостановить выполнение корутины до получения результата # Вернуть результат async def process_data(data): # Обработать данные async def display_data(data): # Отобразить данные на экране data = await load_data("https://example.com ") data = await process_data(data) await display_data(data) 

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

Еще один пример работы сопрограммы

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

Давайте напишем сопрограмму, которая делает запрос к сайту Bing и возвращает полученный HTML-код:

# Импортируем библиотеку для работы с HTTP-запросами import aiohttp # Определяем сопрограмму для запроса к Bing async def get_bing_html(): # Создаем асинхронный HTTP-клиент async with aiohttp.ClientSession() as session: # Делаем GET-запрос к Bing async with session.get("https://www.bing.com ") as response: # Читаем ответ как текст html = await response.text() # Возвращаем HTML-код return html 

Для того, чтобы запустить эту сопрограмму, нам нужно использовать специальную функцию asyncio.run(), которая принимает сопрограмму в качестве аргумента и запускает ее в цикле событий (event loop).

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

# Импортируем библиотеку для работы с асинхронностью import asyncio # Запускаем сопрограмму для запроса к Bing bing_html = asyncio.run(get_bing_html()) # Печатаем длину полученного HTML-кода print(len(bing_html)) 

Когда мы запустим этот код, то увидим, что он работает асинхронно и не блокирует основной поток программы. Более того мы можем делать другие действия, пока ждем ответа от Bing, а также запускать несколько сопрограмм одновременно и ждать их завершения в любом порядке. Давайте напишем еще одну сопрограмму, которая делает запрос к Google и возвращает полученный HTML-код:

# Определяем сопрограмму для запроса к Google async def get_google_html(): # Создаем асинхронный HTTP-клиент async with aiohttp.ClientSession() as session: # Читаем ответ как текст html = await response.text() # Возвращаем HTML-код return html 

Теперь мы можем запустить обе сопрограммы параллельно и ждать их результатов:

# Запускаем обе сопрограммы параллельно и ждем их результатов bing_html, google_html = asyncio.run(asyncio.gather( get_bing_html(), get_google_html() )) # Печатаем длины полученных HTML-кодов print(len(bing_html)) print(len(google_html)) 

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

Различия между корутинами и потоками

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

  • Потоки — это сущности, которые управляются операционной системой
  • Корутины — сущности, которые управляются языком программирования или библиотекой
  • Потоки имеют свой собственный стек памяти
  • Корутины используют общий стек памяти
  • Потоки переключаются между собой прерывисто (preemptively), то есть операционная система может прервать выполнение одного потока и запустить другой в любой момент
  • Корутины переключаются между собой согласованно (cooperatively), то есть программа или язык определяет точки, в которых корутина может приостановиться и передать управление другой
  • Потоки могут выполняться параллельно на нескольких процессорах или ядрах
  • Корутины выполняются последовательно в рамках одного потока
  • Потоки требуют синхронизации доступа к общим данным с помощью механизмов блокировки, таких как мьютексы (mutex) или семафоры (semaphore)
  • Корутины не требуют блокировки данных, так как они не выполняются одновременно

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

Преимущества использования корутин

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

  • Позволяют писать асинхронный код в синхронном стиле, что упрощает чтение и понимание кода.
  • Избавляют от необходимости использовать сложные конструкции, такие как обратные вызовы (callback), промисы (promise) или фьючерсы (future).
  • Экономят память и время, так как они не создают свой собственный стек и не требуют переключения контекста между собой. Это позволяет запускать большое количество корутин на одном потоке без значительных потерь производительности.
  • Поддерживают структурированную параллельность (structured concurrency), то есть они работают в рамках определенной области видимости (scope). Это помогает избежать утечек памяти и ошибок жизненного цикла, так как корутины автоматически отменяются при выходе из своей области видимости.
  • Интегрируются с многими библиотеками и фреймворками для Android, такими как Jetpack, Retrofit и Room. Это позволяет использовать сопрограммы для работы с сетью, базами данных, пользовательским интерфейсом и другими асинхронными операциями.

Недостатки сопрограмм

  • Требуют специального синтаксиса, такого как ключевые слова async и await, для объявления и использования сопрограмм. Такой код не может быть смешан с синхронным кодом без дополнительных преобразований.
  • Не могут выполняться параллельно на нескольких процессорах или ядрах, так как они работают в рамках одного потока. Это означает, что корутины не могут использоваться для задач, которые требуют высокой вычислительной мощности или распределенной обработки.
  • Могут быть сложными для отладки и тестирования, так как они могут приводить к неожиданным результатам из-за асинхронности и кооперативности. Например, корутина может быть отменена в середине выполнения или продолжить выполнение после долгого простоя. Для облегчения отладки и тестирования корутин можно использовать специальные инструменты, такие как Coroutine Debugger или TestCoroutineDispatcher.

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

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

Сегодня публикуем третью часть (первая, вторая) перевода учебного руководства по модулю asyncio в Python. Здесь представлены разделы оригинала №5, 6 и 7.

5. Определение, создание и запуск корутин

В Python-программах можно определять корутины — так же, как определяют новые подпрограммы (функции).

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

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

5.1. Как определить корутину

Корутину можно определить посредством выражения async def .

Это — расширение выражения def , предназначенного для определения подпрограмм.

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

# определение корутины async def custom_coro(): # . 

Корутину, определённую с помощью async def , называют функцией корутины.

Функция корутины: функция, которая возвращает объект корутины. Функцию корутины можно определить, пользуясь командой async def, она может содержать ключевые слова await, async for и async with.

Python glossary

Затем в пределах корутины могут использоваться выражения, специфичные для корутин, такие, как await , async for и async with .

Выполнение Python-корутины может быть приостановлено и возобновлено во многих местах. Выражения await, async for и async with могут быть использованы только в теле функции корутины.

Coroutine function definition

# определение корутины async def custom_coro(): # ожидание другой корутины await asyncio.sleep(1)

5.2. Как создать корутину

После того, как корутина определена, её можно создать.

Выглядит это как вызов функции.

. создание корутины coro = custom_coro() 

В ходе работы этой команды корутина не запускается.

Эта команда возвращает объект корутины.

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

Python in a Nutshell, 2017, с. 516

У Python-объекта корутины есть методы — такие, как send() и close() . Он имеет тип coroutine .

Продемонстрировать это можно, создав экземпляр корутины и выведя сведения о его типе, воспользовавшись встроенной Python-функцией type() :

# SuperFastPython.com # проверка типа корутины # определение корутины async def custom_coro(): # ожидание другой корутины await asyncio.sleep(1) # создание корутины coro = custom_coro() # проверка типа корутины print(type(coro))

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

Нам, кроме того, сообщают об ошибке RuntimeError , так как корутина была создана, но не запускалась. Мы исследуем этот вопрос в следующем разделе.

 sys:1: RuntimeWarning: coroutine 'custom_coro' was never awaited

Объект корутины — это объект, допускающий ожидание.

Это значит, что он представляет Python-тип, реализующий метод await() .

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

Awaitable objects

Подробности об объектах, допускающих ожидание, можно найти здесь.

5.3. Как запустить корутину из Python-кода

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

Цикл событий — это база любого asyncio-приложения. Цикл событий выполняет асинхронные задачи и коллбэки, сетевые операции ввода/вывода, подпроцессы.

Event Loop

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

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

Python in a Nutshell, 2017, с. 517

Типичный способ запуска цикла событий для корутин заключается в использовании функции asyncio.run().

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

# SuperFastPython.com # пример запуска корутины import asyncio # определение корутины async def custom_coro(): # ожидание другой корутины await asyncio.sleep(1) # главная корутина async def main(): # выполнение нашей корутины await custom_coro() # запуск программы, основанной на корутинах asyncio.run(main())

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

6. Цикл событий asyncio

Цикл событий — это сердце программ, основанных на asyncio .

В этом разделе мы поговорим о цикле событий.

6.1. Что такое цикл событий asyncio

Цикл событий — это среда для выполнения корутин в одном потоке.

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

Python Concurrency with asyncio, 2022, с. 3

Цикл событий — это важнейший элемент asyncio-программы.

Он отвечает за решение множества задач. Вот некоторые из них:

  • Выполнение корутин.
  • Выполнение коллбэков.
  • Выполнение сетевых операций ввода/вывода.
  • Выполнение подпроцессов.

«Цикл событий» — это распространённый паттерн проектирования, который стал весьма популярным в наши дни благодаря его использованию в JavaScript.

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

The event loop, Mozilla

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

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

При разработке типичных Python-приложений это не нужно.

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

Разработчикам приложений обычно следует использовать высокоуровневые функции asyncio, такие, как asyncio.run(). У них редко будет возникать необходимость пользоваться ссылкой на объект цикла или вызов его методов.

Event Loop

Модуль asyncio позволяет работать с низкоуровневым API для получения доступа к текущему объекту цикла событий. Этот модуль так же содержит набор методов, которые можно применять для взаимодействия с циклом событий.

Низкоуровневый API предназначен для разработчиков фреймворков, которые могут расширять и дополнять возможности asyncio и интегрировать этот модуль в свои библиотеки.

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

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

6.2. Запуск цикла событий и получение ссылки на его объект

Обычно в asyncio-приложениях ссылки на объекты циклов событий получают, вызывая функцию asyncio.run() .

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

Asyncio Coroutines and Tasks

Эта функция принимает корутину и выполняет её до завершения её работы.

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

Существуют и низкоуровневые функции для создания цикла событий и для работы с ним.

Функция asyncio.new_event_loop() создаёт новый цикл событий и возвращает ссылку на него.

. создаём новый цикл событий asyncio и обеспечиваем доступ к нему loop = asyncio.new_event_loop()

Можно продемонстрировать это всё на рабочем примере.

Здесь мы создаём новый цикл событий и сообщаем сведения о нём.

# SuperFastPython.com # пример создания цикла событий import asyncio # создаём новый цикл событий asyncio и обеспечиваем доступ к нему loop = asyncio.new_event_loop() # сообщаем стандартные сведения о цикле print(loop)

Выполнение этого кода приведёт к созданию цикла событий и к выводу сведений об его объекте.

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

Если цикл событий asyncio уже выполняется — доступ к нему можно получить посредством функции asyncio.get_running_loop().

Возвращает выполняющийся цикл событий в текущем потоке ОС. Если в потоке нет цикла событий — выдаётся ошибка RuntimeError. Эта функция может быть вызвана только из корутины или из коллбэка.

Event Loop

. получаем доступ к выполняющемуся циклу событий loop = asyncio.get_running_loop()

Есть ещё функция, предназначенная для получения или запуска цикла событий. Это — asyncio.get_event_loop(). Но она, в Python 3.10, была признана устаревшей. Пользоваться ей не стоит.

6.3. Подробности об объекте цикла событий

Цикл событий реализован в виде Python-объекта.

Этот объект определяет реализацию цикла событий, он предоставляет стандартный API, предназначенный для взаимодействия с циклом, описанный в классе AbstractEventLoop.

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

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

SelectorEventLoop — это цикл событий, используемый по умолчанию в ОС, основанных на Unix — наподобие Linux и macOS.

ProactorEventLoop — это цикл событий, по умолчанию используемый в Windows.

Сторонние библиотеки могут содержать собственные реализации цикла событий ради его оптимизации под специфические задачи.

6.4. Зачем может понадобиться доступ к циклу событий

Зачем нам обращаться к циклу событий за пределами asyncio-программы?

Это может быть нужно по многим причинам.

  1. Для мониторинга хода выполнения задач.
  2. Для выдачи и получения результатов работы задач.
  3. Для запуска одноразовых задач.

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

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

Теперь, когда мы немного познакомились с циклом событий — перейдём к asyncio-задачам.

7. Создание и запуск asyncio-задач

Объекты Task (задачи) в asyncio-программах можно создавать из корутин.

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

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

Поговорим об asyncio-задачах.

7.1. Что такое asyncio-задача

Task — это объект, который отвечает за планирование выполнения asyncio-корутин и за их независимый запуск.

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

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

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

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

Coroutines and Tasks

Класс asyncio.Task расширяет класс asyncio.Future, его экземпляры являются объектами, допускающими ожидание.

Future — это низкоуровневый класс. Он представляет собой сущность, которая рано или поздно вернёт результат.

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

Coroutines and Tasks

Классы, которые расширяют класс Future , часто называют Future-подобными классами.

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

. подождать завершения задачи await task

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

7.2. Как создать задачу

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

Вспомните — корутину определяют, используя выражение async def . Она выглядит как функция.

# определение корутины async def task_coroutine(): # . 

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

Есть два основных способа создания и планирования задач:

  1. Создать объект Task с использованием высокоуровневого API (предпочтительный способ).
  2. Создать объект Task с помощью низкоуровневого API.

Рассмотрим подробнее каждый из этих способов.

Создание объекта Task с использованием высокоуровневого API

Задачу можно создать, прибегнув к функции asyncio.create_task().

Эта функция принимает экземпляр класса корутины и необязательное имя задачи, а возвращает экземпляр класса asyncio.Task .

. создание корутины coro = task_coroutine() создание задачи из корутины task = asyncio.create_task(coro)

Сделать это можно в одной строке, с помощью сложного выражения.

. создание задачи из корутины task = asyncio.create_task(task_coroutine())

Вот что здесь происходит:

  1. Корутина оборачивается в экземпляр Task .
  2. Планируется выполнение задачи в текущем цикле событий.
  3. Возвращается экземпляр Task .

Ссылку на экземпляр Task можно и не сохранять. С задачей можно взаимодействовать посредством методов, её выполнения можно ожидать в корутине.

Это — предпочтительный способ создания объектов Task из корутин в asyncio-программах.

Создание объекта Task с использованием низкоуровневого API

Задачи можно создавать из корутин и с использованием низкоуровневого API asyncio .

Первый способ такого создания задач заключается в использовании функции asyncio.ensure_future().

Эта функция принимает объект Task , Future , или Future-подобный объект, такой, как корутина, и, необязательно — цикл событий, в котором нужно запланировать выполнение соответствующего объекта.

Если цикл событий не предоставлен — выполнение объекта будет запланировано в текущем цикле событий.

Если этой функции предоставлена корутина — она автоматически оборачивается в экземпляр Task , который и возвращает эта функция.

. создание задачи и планирование её выполнения task = asyncio.ensure_future(task_coroutine())

Ещё одна низкоуровневая функция, которую можно использовать для создания объектов Task и для планирования их выполнения, представлена методом loop.create_task().

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

Можно получить ссылку на экземпляр текущего цикла событий, используемого в asyncio-программе, прибегнув к функции asyncio.get_event_loop().

Затем можно вызвать метод create_task() этого экземпляра цикла для создания экземпляра Task и для планирования его выполнения.

. получить текущий цикл событий loop = asyncio.get_event_loop() создать задачу и запланировать её выполнение task = loop.create_task(task_coroutine())

7.3. Когда запускаются задачи?

Распространённый вопрос о работе с задачами звучит так: «Когда, после того, как задача создана, она запускается?».

И это — хороший вопрос.

Хотя мы можем планировать независимый запуск корутин в виде задач, пользуясь функцией create_task() , задача может не запуститься немедленно.

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

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

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

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

. создание задачи из корутины task = asyncio.create_task(task_coroutine()) ожидание задачи, что позволяет ей запуститься await task

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

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

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

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

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

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