Что такое корутины kotlin
Перейти к содержимому

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

  • автор:

Корутины

В последнее время поддержка асинхронности и параллельных вычислений стала неотъемлимой чертой многих языков программирования. И Kotlin не является исключением. Зачем нужны асинхронность и параллельные вычисления? Параллельные вычисления позволяют выполнять несколько задач одновременно, а асинхронность позволяет не блокировать основной ход приложения во время выполнения задачи, которая занимает продолжительное время. Например, мы создаем графическое приложение для десктопа или мобильного устройства. И нам надо по нажатию на кнопку отправлять запрос к интернет-ресурсу. Однако подобный запрос может занять продолжительное время. И чтобы приложение не зависало на период отправки запроса, подобные запросы к интернет-ресурсам следует отправлять асинхронно. При асинхронных запросах пользователь не ждет пока придет ответ от интернет-ресурса, а продолжает работу с приложением, а при получении ответа получит соответствующее уведомление.

В языке Kotlin поддержка асинхронности и параллельных вычислений воплощена в виде корутин ( coroutine ). По сути корутина представляет блок кода, который может выполняться параллельно с остальным кодом. А базовая функциональность, связанная с корутинами, сосредоточена в библиотеке kotlinx.coroutines .

Рассмотрим определение и применение корутины на простейшем примере.

Добавление kotlinx.coroutines

Прежде всего стоит отметить, что функциональность корутин (библиотека kotlinx.coroutines ) по умолчанию не включена в проект. И нам ее надо добавить. Если мы создаем проект консольного приложения в IntelliJ IDEA, то мы можем добавить соответствующую библиотеку в проект. Для этого в меню File перейдем к пункту Project Structure..

Добавление в проект kotlinx.coroutines

Далее на вкладке «Project Settings» перейдем к пункту Libraries . В центральном поле отобразятся библиотеки, добавленные в проект.

Добавление библиотеки kotlinx.coroutines в проект на Kotlin

И для добавления новой библиотеки нажмем на знак плюса и в контекстном меню выберем пункт From Maven.

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

Добавление библиотеки kotlinx-coroutines-core-jvm в проект на Kotlin

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

Отметим все необходимые флажки и нажмем на кнопку OK

Добавление библиотеки kotlinx-coroutines-core-jvm через Maven в проект на Kotlin

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

библиотека kotlinx-coroutines-core-jvm в проекте на Kotlin

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

Далее в этой папке выберем библиотеку kotlinx-coroutines-core-jvm.jar и нажмем на OK для ее добавления:

Добавление в проект на kotlinx-coroutines-core-jvm.jar

Определение 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() ), корутина возобновляет свою работу в одном из свободных потоков.

Корутины (Сопрограммы). Часть первая

Корутины предназначены для асинхронного программирования и позволяют отказаться от методов обратного вызова (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 решает эту проблему гибким способом, предоставляя поддержку корутин на уровне языка и делегирование большей части функциональности библиотекам.

В дополнение к возможности асинхронного программирования, корутины также предоставляют множество других возможностей, таких как параллелизм и акторы (действующие субъекты, ориг.: actors).

Как начать

Недавно начали изучать Kotlin? Посетите Начало работы с Kotlin.

Документация

  • Руководство по корутинам
  • Основы
  • Каналы
  • Контекст корутин и диспатчеры
  • Разделяемое изменяемое состояние и параллелизм
  • Асинхронный поток

Руководства

  • Методы асинхронного программирования
  • Введение в корутины и каналы
  • Отладка корутинов с помощью IntelliJ IDEA
  • Отладка Kotlin Flow с помощью IntelliJ IDEA – руководство

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

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

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

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

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

Пример корутины на Kotlin

Конструкция корутины в Kotlin основана на использовании ключевого слова suspend и библиотеки kotlinx.coroutines . Вот пример простой корутины на Kotlin:

import kotlinx.coroutines.* fun main() < // Запускаем корутину внутри функции main GlobalScope.launch < println("Начало выполнения корутины") delay(1000) // Приостанавливаем выполнение на 1 секунду println("Корутина возобновлена после приостановки") >println("Выполнение основного кода") // Ждем, пока корутина завершится (необязательно в реальном коде) Thread.sleep(2000) >

В этом примере мы создаем корутину с помощью launch из kotlinx.coroutines.GlobalScope . Корутина выводит сообщение, приостанавливается на 1 секунду с помощью delay и затем выводит еще одно сообщение. После создания корутины, мы продолжаем выполнение основного кода и ожидаем 2 секунды, чтобы дать корутине завершиться.

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

Выполнение основного кода Начало выполнения корутины Корутина возобновлена после приостановки

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

Корутины против функций

Преимущества корутин

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

  1. Легковесность: Корутины имеют более низкий уровень накладных расходов по сравнению с потоками. В отличие от потоков, которые требуют выделения отдельного стека и контекстного переключения, корутины используют общий поток выполнения, что делает их более легковесными и экономичными в плане ресурсов.
  2. Асинхронность и удобство программирования: Корутины облегчают написание асинхронного кода. Они позволяют написать последовательный код, который приостанавливается при выполнении долгих операций ввода-вывода или ожидании результатов других задач. Это улучшает читаемость и поддерживаемость кода, так как не требуется использование сложных конструкций обратных вызовов или цепочек промисов.
  3. Управление потоком выполнения: Корутины позволяют легко управлять потоком выполнения. Вы можете приостанавливать, возобновлять и передавать управление между корутинами в явном виде, что делает код более понятным и позволяет эффективно управлять конкурентными задачами.
  4. Обработка ошибок: Корутины обладают мощными механизмами обработки ошибок. Они предоставляют конструкции для обработки исключений и отмены операций, позволяя удобно обрабатывать ошибки и сбои в асинхронном коде.
  5. Интеграция с экосистемой: В некоторых языках программирования, таких как Kotlin, корутины являются частью официальной стандартной библиотеки или стандарта языка. Они широко поддерживаются и интегрируются с другими библиотеками и фреймворками, что облегчает разработку асинхронных приложений.
  6. Параллелизм и масштабируемость: Корутины позволяют создавать параллельные задачи и обрабатывать их эффективно. Можно запускать множество корутин и автоматически распределять их выполнение по доступным ресурсам, что позволяет достичь более высокой производительности и масштабируемости.

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

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

  1. Асинхронные операции ввода-вывода: Корутины отлично подходят для выполнения асинхронных операций ввода-вывода, таких как сетевые запросы, операции с базой данных или файловой системой. Они позволяют писать последовательный и понятный код, который приостанавливается во время ожидания результатов операций ввода-вывода.
  2. Параллельные задачи: Корутины позволяют легко запускать параллельные задачи и управлять их выполнением. Вы можете запускать несколько корутин одновременно и асинхронно ожидать их завершения. Это особенно полезно при выполнении независимых операций, таких как параллельная обработка данных или обращение к нескольким сервисам одновременно.
  3. Цепочки обработки данных: Они позволяют создавать удобные цепочки обработки данных. Вы можете использовать корутины для пошаговой обработки больших объемов данных без блокирования основного потока выполнения. Это особенно полезно при обработке потоков данных, таких как чтение из файлов, обработка и агрегация данных.
  4. Обработка событий: Корутины могут быть эффективным инструментом для обработки событий, таких как пользовательский ввод, события сети или сигналы операционной системы. Вы можете использовать корутины для асинхронного ожидания и обработки событий, сокращая сложность кода по сравнению с традиционными обратными вызовами или циклами событий.
  5. Таймауты и отмена операций: Они предоставляют удобные средства для работы с таймаутами и отменой операций. Вы можете использовать механизмы отмены корутин для прерывания долгих операций или установки временных ограничений на выполнение.
  6. Тестирование и отладка: Корутины облегчают тестирование и отладку асинхронного кода. Вы можете управлять выполнением корутин, приостанавливать и возобновлять их для проверки различных состояний и вариантов выполнения. Это делает код более предсказуемым и тестируемым.

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

Недостатки корутин

Хотя технология предоставляет много преимуществ, у нее также есть некоторые недостатки:

  1. Сложность отладки: Поскольку корутины выполняются асинхронно и могут приостанавливаться и возобновляться в разных местах, отладка кода, содержащего корутины, может быть сложной. Трассировка стека может быть запутанной, особенно при сложных сценариях взаимодействия между корутинами.
  2. Возможность утечек ресурсов: Если корутина неправильно управляет ресурсами, такими как открытые файлы или сетевые соединения, это может привести к утечкам ресурсов. Корутины должны быть аккуратно управляемыми и освобождать ресурсы при завершении.
  3. Затраты на память: Каждая корутина требует дополнительных ресурсов памяти для сохранения своего состояния и контекста выполнения. При большом количестве корутин это может потребовать дополнительного объема памяти.
  4. Потенциальные проблемы с синхронизацией: Если необходимо обеспечить синхронизацию доступа к общим ресурсам из разных корутин, это может потребовать использования механизмов синхронизации, таких как блокировки или атомарные операции. Неправильное использование синхронизации может привести к проблемам с состоянием гонки или блокировками.
  5. Необходимость библиотеки поддержки: В большинстве языков программирования для работы с корутинами требуется использование специальной библиотеки, что может добавить зависимости к проекту и усложнить его сборку и обслуживание.

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

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

  1. Ограниченные ресурсы: Если у вас есть ограниченное количество ресурсов, таких как потоки или память, и требуется создание огромного количества корутин, это может привести к исчерпанию ресурсов и снижению производительности системы. В таких случаях, использование потоков может быть более подходящим, поскольку они обычно имеют более прямое соответствие с ресурсами операционной системы.
  2. Большие объемы вычислений: Если вам требуется выполнить вычисления, которые являются вычислительно интенсивными и занимают значительное время процессора, корутины могут не быть самым эффективным решением. В таких случаях использование многопоточности и распределенных вычислений может быть предпочтительнее.
  3. Большой объем данных: Если вам нужно обрабатывать большие объемы данных, которые не умещаются в память, корутины могут быть не самым подходящим инструментом. Вместо этого, использование потоков и пакетной обработки данных может быть более эффективным.
  4. Интерфейсы с блокирующими операциями: Если вам нужно работать с внешними интерфейсами, которые требуют блокирования при выполнении операций ввода-вывода, например, синхронные вызовы баз данных или сетевые запросы, то использование корутин может не принести существенных преимуществ. В этом случае можно использовать асинхронные API или потоки.
  5. Сложность программы: Если ваша программа уже достаточно сложная и содержит сложную логику с параллельными или конкурирующими задачами, использование корутин может усложнить код и управление его состоянием. В таких случаях, возможно, более простые абстракции, такие как потоки или асинхронные функции, могут быть предпочтительнее.

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

Что еще почитать про корутины

  • Миграция с Rx на корутины — опыт «Тинькофф Мобайл»
  • Онлайн-собеседование, корутины
  • Бесплатный курс по корутинам
  • Coroutine Recipes: песочница корутинов

Если вы нашли опечатку — выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.

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

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