Корутины в 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
Разберём как работает код:
- Мы создали 2 корутины: image_downloader и music_downloader . Первая качает картинку по ссылке https://www.some-images.com/image1.jpg , вторая — музыку по ссыке https://www.music-site.com/artist/album/song5.mp3 .
- Мы положили их в список coroutines
- В бесконечном цикле мы по очереди запускаем все корутины из списка. Если вышла ошибка StopIteration — корутина истощилась, т.е. файл скачан. Убираем её из списка, корутина больше запускаться не будет.
- Чтобы итерация по списку coroutines не сбивалась после удаления элемента из него итерируем не по оригиналу, а по копии coroutines.copy() .
- Если список с корутинами закончился (его длина равна нулю), пора заканчивать и бесконечный цикл, потому что все файлы скачаны.
Передать параметры в асинхронную функцию
В плане аргументов асинхронные функции ничем не отличаются от обычных. Доработаем пример со счетчиком и вместо 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
Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.
Переходите на страницу учебных модулей «Девмана» и выбирайте тему.
Корутины (Сопрограммы). Часть первая
Корутины предназначены для асинхронного программирования и позволяют отказаться от методов обратного вызова (Callback) и RxJava. Самый простой пример применения — вам нужно загрузить картинку с сервера и показать её в ImageView — это две строчки кода. Проблема в том, что на загрузку требуется время и вторая строчка кода должна выполниться не сразу, а после выполнения первой строчки. Нужен механизм ожидания выполнения задачи, после которой можно продолжать работу.
Код с корутинами будет выглядеть в общем виде следующим образом.
launch(Dispatchers.Main) < val image = withContext(Dispatchers.IO) < getImage() >// получить из контекста IO imageView.setImageBitmap(image) // Возвращаемся в главный поток >
Пока функция getImage() выполняется в выделенном пуле потоков IO, главный поток свободен и может выполнять любую другую задачу. Функция withContext() приостанавливает текущую корутину, запущенную через launch(), пока работает getImage(). Как только getImage() выполнит свою задачу, корутина возобновит работу в главном потоке и вызовет imageView.setImageBitmap(image).
Корутины являются аналогом потоков, но более легковесны и проще. Во многих случаях они предпочтительнее потоков, если не требуется интенсивной работы.
Немного теории
Современные операционные системы умеют работать с несколькими потоками в каждом процессе. Поток является абстрактным понятием, создающим иллюзию раздельной работы многих участков кода. Но нужно уметь работать с потоками, чтобы избежать проблем, связанных с кодом, который может блокировать другой код.
В стандартном приложении у вас есть основной поток, в котором мы взаимодействуем с компонентами (кнопки, картинки). Если основной поток заблокирован, то приложение замирает и раздражает пользователя. Особенно ярко проблема выражается, когда приложение требует данных из интернета — это узкое горлышко для приложения. Вам приходится ждать ответа от сервера и приложение вынуждено простаивать.
Другой вариант блокирующего потока — интенсивные вычисления. Даже мощный процессор можно загрузить настолько сложными данными, что он затормозит приложение.
Сами по себе потоки являются тяжёлым ресурсом для системы. В некоторых случаях от потоков нельзя отказаться, других вариантов просто нет. Но у вас есть выбор, какой именно поток заблокировать, чтобы дать свободу другому потоку. Очевидно, не стоит блокировать основной поток приложения. Кроме того, можно вообще не блокировать поток, а позволить ему работать асинхронно, не мешая другому потоку.
Чтобы показать преимущество корутин перед потоками, разработчики из JetBrains приводили следующий пример — Запустим 10 тысяч корутин. Система без труда справится с этой задачей. С таким же количеством потоков программа закроется от нехватки памяти.
(1..10000).forEach < GlobalScope.launch < val threadName = Thread.currentThread().name println("\$it printed on thread \$") > > Thread.sleep(1000)
В целом, корутины — это блок кода, который выполняется асинхронно без блокировки потока, из которого корутина была запущена. Служит заменой устаревшего AsyncTask или схожих технологий в виду своей эффективности.
Сами по себе корутины не являются чем-то новым, они в той или иной форме уже были в других языках программирования даже в 1960-х годах. Kotlin за кулисами использует многопоточность в корутинах в оптимальном режиме.
К сожалению, порог вхождения в корутины очень высок. При знакомстве на котанов (т.е. на нас) обрушивают просто поток (и тут поток, да что же это такое) информации из непонятных и страшных слов. Без стакана (молока) не разберёшься.
Корутины
В последнее время поддержка асинхронности и параллельных вычислений стала неотъемлимой чертой многих языков программирования. И Kotlin не является исключением. Зачем нужны асинхронность и параллельные вычисления? Параллельные вычисления позволяют выполнять несколько задач одновременно, а асинхронность позволяет не блокировать основной ход приложения во время выполнения задачи, которая занимает продолжительное время. Например, мы создаем графическое приложение для десктопа или мобильного устройства. И нам надо по нажатию на кнопку отправлять запрос к интернет-ресурсу. Однако подобный запрос может занять продолжительное время. И чтобы приложение не зависало на период отправки запроса, подобные запросы к интернет-ресурсам следует отправлять асинхронно. При асинхронных запросах пользователь не ждет пока придет ответ от интернет-ресурса, а продолжает работу с приложением, а при получении ответа получит соответствующее уведомление.
В языке Kotlin поддержка асинхронности и параллельных вычислений воплощена в виде корутин ( coroutine ). По сути корутина представляет блок кода, который может выполняться параллельно с остальным кодом. А базовая функциональность, связанная с корутинами, сосредоточена в библиотеке kotlinx.coroutines .
Рассмотрим определение и применение корутины на простейшем примере.
Добавление kotlinx.coroutines
Прежде всего стоит отметить, что функциональность корутин (библиотека kotlinx.coroutines ) по умолчанию не включена в проект. И нам ее надо добавить. Если мы создаем проект консольного приложения в IntelliJ IDEA, то мы можем добавить соответствующую библиотеку в проект. Для этого в меню File перейдем к пункту Project Structure..
Далее на вкладке «Project Settings» перейдем к пункту Libraries . В центральном поле отобразятся библиотеки, добавленные в проект.

И для добавления новой библиотеки нажмем на знак плюса и в контекстном меню выберем пункт From Maven.
После этого нам откроется окно для добавления библиотеки через Maven. В этом окне в поле ввода введем название нужной нам библиотеки — kotlinx-coroutines-core-jvm и нажмем на кнопку поиска. Если соответствующая библиотека найдена, то нам отобразится выпадающий список с результатами

Выберем из него последнюю версию, которая называется наподобие org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4 — в данном случае используется версия 1.6.4, но конкретный номер версии может отличаться.
Отметим все необходимые флажки и нажмем на кнопку OK

После установки библиотеки мы сможем найти ее файл в списке библиотек

В качестве альтернативы мы могли бы вручную подключить нужную библиотеку из локального хранилища. Так, на Windows это будет папка C:\Users\[Имя_пользователя]\AppData\Roaming\JetBrains\IdeaIC[номер_версии]\plugins\Kotlin\kotlinc\lib
Далее в этой папке выберем библиотеку kotlinx-coroutines-core-jvm.jar и нажмем на OK для ее добавления:

Определение suspend-функции
Сначала рассмотрим пример, который не использует корутины:
import kotlinx.coroutines.* suspend fun main() < for(i in 0..5)< delay(400L) println(i) >println("Hello Coroutines") >
Здесь в функции main перебираем последовательность от 0 до 5 и выводит текущий элемент последовательности на консоль. Для имитации продолжительной работы внутри цикла вызываем специальную функцию delay() из пакета kotlinx.coroutines . В эту функцию передается количество миллисекунд, на которое выполняется задержка. Передаваемое значение должно иметь тип Long. То есть здесь функция будет выполнять задержку в 400 миллисекунд перед выводом на консоль текущего элемента последовательности.
После выполнения работы цикла выводим на консоль строку «Hello Coroutines».
И чтобы использовать внутри функции main функцию delay() , функция main предваряется модификатром suspend . Модификатор suspend определяет функцию, которая может приостановить свое выполнение и возобновить его через некоторый период времени.
Сама функция delay() тоже является подобной функцией, которая определена с модификатором suspend . А любая функция с модификатором suspend может вызываться либо из другой функции, которая тоже имеет модификатор suspend , либо из корутины.
Если мы запустим приложение, то мы увидим следующий консольный вывод:
0 1 2 3 4 5 Hello Coroutines
Здесь мы видим, что строка «Hello Coroutines» выводится после выполнения цикла. Но вместо цикла у нас могла бы быть более содержательная, но и более продолжительная работа, например, обращение к интернет-ресурсу, к удаленой базе данных, какие-то операции с файлами и т.д. И в этом случае все определенные после этой работы действия ожидали бы завершения этой продолжительной работы, как в данном случае строка «Hello Coroutines» ждет завершения цикла.
Определение корутины
Теперь вынесем продолжительную работу — то есть цикл в корутину:
import kotlinx.coroutines.* suspend fun main() = coroutineScope < launch< for(i in 0..5)< delay(400L) println(i) >> println("Hello Coroutines") >
Прежде всего для определения и выполнения корутины нам надо определить для нее контекст, так как корутина может вызываться только в контексте корутины (coroutine scope). Для этого применяется функция coroutineScope() — создает контекст корутины. Кроме того, эта функция ожидает выполнения всех определенных внутри нее корутин. Стоит отметить, что coroutineScope() может применяться только в функции с модификатором suspend , коей является функция main.
Сама корутина определяется и запускается с помощью построителя корутин — функции launch . Она создает корутину в виде блока кода — в данном случае это:
и запускает эту корутину параллельно с остальным кодом. То есть данная корутина выполняется независимо от прочего кода, определенного в функции main.
В итоге при выполнении программы мы увидим несколько другой консольный вывод:
Hello Coroutines 0 1 2 3 4 5
Теперь строка «Hello Coroutines» не ожидает, пока завершится цикл, а выполняется параллельно с ним.
Вынесение кода корутин в отдельную функцию
Выше код корутины располагался непосредственно в функции main. Но также можно определить его в виде отдельной функции и вызывать в корутине эту функцию:
import kotlinx.coroutines.* suspend fun main()= coroutineScope < launch< doWork() >println("Hello Coroutines") > suspend fun doWork() < for(i in 0..5)< println(i) delay(400L) >>
В данном случае основной код корутины вынесен в функцию doWork() . Поскольку в этой функции применяется функция delay() , то doWork() определена с модификатором suspend . Сама корутина создается также с помощью функции launch() , которая вызывает функцию doWork() .
Обратите внимание, что в примере выше в конце функции main вызывается функция println() , которая выводит строку на консоль. Если мы ее удалим, то мы столкнемся с ошибкой — функция main должна возвращать значение Unit. В этом случае мы можем либо явным образом возвратить значение Unit:
import kotlinx.coroutines.* suspend fun main()= coroutineScope < launch< for(i in 0..5)< println(i) delay(400L) >> Unit >
Либо можно типизировать функцию coroutineScope типом Unit:
import kotlinx.coroutines.* suspend fun main()= coroutineScope < launch< for(i in 0..5)< println(i) delay(400L) >> >
Корутины и потоки
В ряде языков программирования есть такие структуры, которые позволяют использовать потоки. Однако между корутинами и потоками нет прямого соответствия. Корутина не привязана к конкретному потоку. Она может быть приостановить выполнение в одном потоке, а возобновить выполнение в другом.
Когда корутина приостанавливает свое выполнение, например, как в случае выше при вызове задержки с помощью функции delay() , эта корутина освобождает поток, в котором она выполнялась, и сохраняется в памяти. А освобожденный поток может быть зайдествован для других задач. А когда завершается запущенная задача (например, выполнение функции delay() ), корутина возобновляет свою работу в одном из свободных потоков.
Coroutine
Корутины (coroutines), или сопрограммы — это блоки кода, которые работают асинхронно, то есть по очереди. В нужный момент исполнение такого блока приостанавливается с сохранением всех его свойств, чтобы запустился другой код. Когда управление возвращается к первому блоку, он продолжает работу. В результате программа выполняет несколько функций одновременно.

«IT-специалист с нуля» наш лучший курс для старта в IT
Для чего нужны корутины
- Для создания асинхронных приложений, которые могут выполнять несколько действий одновременно.
- Для гибкой и удобной реализации многозадачности.
- Для большего контроля при переключении между разными задачами. Корутинами управляют разработчик и программа, а не операционная система.
- Для снижения нагрузки на аппаратные ресурсы устройства.
Корутины поддерживают Kotlin, JavaScript, PHP, C#, Go, Python, Ruby, Lua и другие языки программирования.
Профессия / 8 месяцев
IT-специалист с нуля
Попробуйте 9 профессий за 2 месяца и выберите подходящую вам

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

- Могут иметь несколько точек для входа и возврата.
- Приостанавливаются и возобновляют работу больше одного раза.
- Действуют по стратегии LIFO — последняя вызванная корутина закончится первой.
Примеры использования корутин в Java и Kotlin
Пример ниже создает две корутины в Java: одну для выполнения в фоновом потоке и другую для выполнения в основном потоке. Корутины выполняются асинхронно, и можно увидеть, как они и основной поток выполняются параллельно:
import kotlinx.coroutines.CoroutineScope; import kotlinx.coroutines.Dispatchers; import kotlinx.coroutines.launch; public class Main < public static void main(String[] args) < // Создаем область корутин CoroutineScope scope = CoroutineScope(Dispatchers.Default); // Запускаем корутину scope.launch(() -> < // Код, выполняющийся в корутине for (int i = 0; i < 5; i++) < System.out.println("Корутина: " + i); try < Thread.sleep(1000); // Имитация работы >catch (InterruptedException e) < e.printStackTrace(); >> >); // Код основного потока for (int i = 0; i < 5; i++) < System.out.println("Основной поток: " + i); try < Thread.sleep(1000); // Имитация работы >catch (InterruptedException e) < e.printStackTrace(); >> // Ожидаем завершения всех корутин scope.close(); > >
А в этом примере для Kotlin создается корутина с помощью функции launch из библиотеки kotlinx.coroutines и в ней выполняется асинхронная работа с использованием функции delay . Затем приостанавливается основной поток с помощью Thread.sleep , чтобы дать корутине время выполнить свою работу:
import kotlinx.coroutines.* fun main() < // Запускаем корутину в главном потоке GlobalScope.launch < println("Корутина: Начало работы") // Приостанавливаем корутину на 1 секунду delay(1000) println("Корутина: Прошло 1 секунда") // Приостанавливаем корутину еще на 1 секунду delay(1000) println("Корутина: Прошло еще 1 секунда") // Завершаем корутину println("Корутина: Завершение работы") >// Код основного потока println("Основной поток: Начало работы") // Приостанавливаем основной поток на 2 секунды Thread.sleep(2000) println("Основной поток: Прошло 2 секунды") // Основной поток завершается, но корутина продолжает работать // Завершаем корутину после завершения основного потока println("Основной поток: Завершение работы") >
Обратите внимание, что корутина продолжает работать даже после завершения основного потока.
Различия между корутинами и потоками
Многозадачность с использованием корутин легко перепутать с многопоточностью. Это выполнение программы в нескольких системных потоках.
Поток — составная часть процесса, который выполняется в операционной системе. Принцип похож: несколько потоков останавливаются и возобновляются, ждут друг друга, общаются. Но есть отличия.

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

Курс для новичков «IT-специалист
с нуля» – разберемся, какая профессия вам подходит, и поможем вам ее освоить
Снижение нагрузки на систему
Асинхронность позволяет выполнять несколько действий в рамках одного потока вместо того, чтобы множить потоки. Системе проще выполнить один поток, чем несколько. Разработчики из JetBrains прояснили это на таком примере: запустить 10 тысяч корутин одновременно — задача, с которой устройство справится. А 10 тысяч потоков — невозможное явление: компьютеру не хватит памяти.
Гибкость в управлении
Переключение между корутинами происходит вручную. Программист сам прописывает этот момент в коде. Поэтому управление корутинами можно контролировать, что дает разработчику больше возможностей. Нет риска, что программа переключится между блоками кода в неподходящий момент, как это бывает с менее гибкими решениями.
Недостатки сопрограмм
Сложность и высокий порог вхождения
Разобраться в работе корутин и научиться писать асинхронный код может быть сложно. Поэтому сопрограммы начинают изучать специалисты, которые уже хорошо разобрались в базовых принципах выбранного языка.
Узкая специализация
Корутины — высокоуровневое решение. Ускорить сложные вычисления с помощью сопрограмм не получится. В этом случае нужна многопоточность, а не асинхронность. При этом корутины подходят для снижения нагрузки на систему.
Отсутствие в ряде языков
Корутины трудно реализовать в языках, которые их не поддерживают. Для этого нужно использовать сторонние решения или преобразовывать код на одном языке в другой. Это усложняет разработку.
IT-специалист с нуля
Наш лучший курс для старта в IT. За 2 месяца вы пробуете себя в девяти разных профессиях: мобильной и веб-разработке, тестировании, аналитике и даже Data Science — выберите подходящую и сразу освойте ее.
