Что такое корутины kotlin
Корутины
Введение в корутины
В последнее время поддержка асинхронности и параллельных вычислений стала неотъемлимой чертой многих языков программирования. И Kotlin не является исключением. Зачем нужны асинхронность и параллельные вычисления? Параллельные вычисления позволяют выполнять несколько задач одновременно, а асинхронность позволяет не блокировать основной ход приложения во время выполнения задачи, которая занимает продолжительное время. Например, мы создаем графическое приложение для десктопа или мобильного устройства. И нам надо по нажатию на кнопку отправлять запрос к интернет-ресурсу. Однако подобный запрос может занять довольно много время. И чтобы приложение не зависало на период отправки запроса, подобные запросы к интернет-ресурсам следует отправлять асинхронно. При асинхронных запросах пользователь не ждет пока придет ответ от интернет-ресурса, а продолжает работу с приложением, а при получении ответа получит соответствующее уведомление.
Рассмотрим определение и применение корутины на простейшем примере.
Добавление kotlinx.coroutines
Прежде всего стоит отметить, что функциональность корутин (библиотека kotlinx.coroutines ) по умолчанию не включена в проект. И нам ее надо добавить. Если мы создаем проект консольного приложения в IntelliJ IDEA, то мы можем добавить соответствующую библиотеку в проект. Для этого в меню File перейдем к пункту Project Structure..
После добавления в проекте в узле External Libraries / KotlinJavaRuntime мы увидим добавленную библиотеку:
В других типах проектов для подключения kotlinx.coroutines может использоваться Gradle или другие способы. Так, если проект использует Gradle, то в файл gradle добавляется зависимость:
Определение suspend-функции
Сначала рассмотрим пример, который не использует корутины:
После выполнения работы цикла выводим на консоль строку «Hello Coroutines».
Если мы запустим приложение, то мы увидим следующий консольный вывод:
Здесь мы видим, что строка «Hello Coroutines» выводится после выполнения цикла. Но вместо цикла у нас могла бы быть более содержательная, но и более продолжительная работа, например, обращение к интернет-ресурсу, к удаленой базе данных, какие-то операции с файлами и т.д. И в этом случае все определенные после этой работы действия ожидали бы завершения этой продолжительной работы, как в данном случае строка «Hello Coroutines» ждет завершения цикла.
Определение корутины
и запускет эту корутину параллельно с остальным кодом. То есть данная корутина выполняется независимо от прочего кода, определенного в функции main.
В итоге при выполнении программы мы увидим несколько другой консольный вывод:
Теперь строка «Hello Coroutines» не ожидает, пока завершится цикл, а выполняется параллельно с ним.
Вынесение кода корутин в отдельную функцию
Выше код корутины располагался непосредственно в функции main. Но также можно определить его в виде отдельной функции и вызывать в корутине эту функцию:
Корутины и потоки
В ряде языков программирования есть такие структуры, которые позволяют использовать потоки. Однако между корутинами и потоками нет прямого соответствия. Корутина не привязана к конкретному потоку. Она может быть приостановить выполнение в одном потоке, а возобновить выполнение в другом.
Корутины в Kotlin (гайд)
Simon Wirtz в своем блоге публикует достаточно много интересных постов о Kotlin.
Представляю вашему вниманию перевод одного из них.
Введение и мотивация
Как я уже упоминал в Twitter несколько дней назад, я планировал подробнее посмотреть корутины из Kotlin, что я и сделал. Но, к сожалению, это заняло больше времени, чем я ожидал. По большей части это связано с тем, что корутины — это очень объемная тема, в особенности, если вы не знакомы с их концепцией. Так или иначе, хочу поделиться своим взглядом с вами и надеюсь преподнести вам исчерпывающий обзор.
Корутины — это, несомненно, одна из “больших фич”, как сказано в блоге JetBrains:
Другими словами, корутины были представлены для простой реализации многопоточного программирования. Наверняка многие из вас работали с Java, ее Thread-классом и классами для многопоточного программирования. Я и сам много с ними работал и убежден в зрелости их решений.
Многопоточность Java vs Kotlin корутины
Если вы все еще испытываете сложности с потоками и многопоточностью в Java, то я вам рекомендую книгу Java Concurrency in Practice.
Конечно, реализация из Java, с инженерной точки зрения, хорошо спроектирована, но ее сложно использовать в повседневной работе и она достаточно многословна. Помимо этого, в Java не так много реализаций для не блокирующего программирования. Часто можно себя поймать на том, что, запуская поток, ты совсем забываешь, что быстро попадаешь в блокирующий код (на блокировках, ожиданиях и т.д.) Альтернативный не блокирующий подход тяжело применять в повседневной работе, и в нем легко ошибиться.
Корутины, с другой стороны, выглядят как простой последовательный код, пряча всю сложность внутри библиотек. В то же время они предоставляют возможность запускать асинхронный код без всяких блокировок, что открывает большие возможности для различных приложений. Вместо блокировки потоков вычисления становятся прерываемыми. JetBrains описывают корутины как “легковесные потоки”, конечно, не те Threads, что мы знаем в Java. корутины очень дешевы в создании, и накладные расходы в сравнении с потоками не идут ни в какое сравнение. Как вы дальше увидите, корутины запускаются в Threads под управлением библиотеки. Другое весомое отличие — ограничения. Количество потоков ограничено, так как они на самом деле соответствуют нативным потокам. Создание корутины, с другой стороны, практически бесплатно, и даже тысячи их могут быть легко запущены.
Стили многопоточного программирования
В различных языках можно встретить самые разные подходы к многопоточному программированию: основанные на callback (JavaScript), future/promise (Java, JavaScript), async/await подход (С#) и т.д. Все они могут быть реализованы при помощи корутин благодаря тому, что они не навязывают стиль программирования. Напротив, любой стиль либо уже реализован, либо может быть реализован с их помощью.
Концепция корутин
Нельзя сказать, что понятие “корутины” новое. Согласно статье из Wikipedia, само название уже было известно в 1958 году. Многие современные языки программирования предоставляют нативную поддержку: C#, Go, Python, Ruby, и т.д. Реализация корутин, и в Kotlin в том числе, часто основана на так называемых “Continuations”, которые являются абстрактным представлением управляемого состояния в компьютерных программах. Мы еще вернемся к тому, как они работают (реализация корутин).
Начало. Основы
Есть исчерпывающий материал, доступный на сайте kotlinlang.org, в котором хорошо описано, как настроить проект для работы с корутинами. Посмотрите подробней на материал по предыдущей ссылке или просто возьмите за основу код из моего репозитория на GitHub.
Ингредиенты корутин
Как уже было сказано, библиотека с корутинами предоставляет понятный высокоуровневый API, который позволяет нам быстро начать работу. Единственный модификатор, который нужно выучить, это suspend. Он используется в качестве дополнительного модификатора у методов, чтобы пометить их как прерываемые.
Прерываемые функции
Как видно из примера выше, функция с прерыванием выглядит как обычная функция с дополнительным модификатором. Имейте в виду, что такие функции могут быть вызваны только из корутин, иначе это приведет к ошибкам компиляции.
Корутины могут быть как последовательностью обычных функций, так и функций с прерыванием с необязательным результатом, который будет доступен после выполнения.
Перейдем к примерам
После всех бла-бла-бла перейдем к конкретным примерам. Начнем с основ:
Заглянем глубже в кроличью нору
Рассмотрим пример, более приближенный к реальности. Представьте, что в приложении вам нужно отправить email. Запрос получателей и генерация текста сообщения занимают значительное время. Оба процесса независимы, и соответственно мы можем их выполнять параллельно.
Можно немного упростить код автора
async билдер
Мы уже видели “ожидающую” часть Deferred объектов из Kotlin в (7), где прерываемая функция вызывалась с результатами ожидания обоих методов. Метод await() вызывается на экземпляре объекта Deferred, вызов которого прерывается до того, пока не будет доступен результат. Вызов sendEmail также обернут в асинхронный билдер, чтобы мы могли подождать выполнения.
Что пропустили: CoroutineContext
Общее изменяемое состояние
Вы, наверное, уже задумались: корутины выглядят, конечно, хорошо, но как же мы будем выполнять синхронизацию и как мы будем обмениваться данными между различными корутинами?
Что ж, это как раз тот вопрос, которым я недавно задавался, и это резонный вопрос для большинства корутин, использующих пул потоков. Для синхронизации мы можем использовать различные техники: потокобезопасные структуры данных, ограничение на выполнение в одном потоке или использовать блокировки (посмотрите подробнее Mutex )
Помимо общих паттернов, корутины из Kotlin поощряют нас использовать стиль “обмен через коммуникацию” (смотрите QA).
В частности, для коммуникации хорошо подходит “актор”. Его можно использовать в корутинах, которые могут отправлять/принимать сообщения от него. Давайте посмотрим на примере:
Акторы
Каналы (Channel)
Channels предоставляют нам возможность обмена потоком значений, что очень похоже на то, как мы используем BlockingQueue (реализуя паттерн producer-consumer) в Java, только без всяких блокировок. Кроме того, send и receive являются прерываемыми функциями и используются для получения и отправки сообщений через канал, реализующий FIFO стратегию.
Как мы видим, все состояние изолировано в конкретном акторе. Это решает проблему общего изменяемого состояния.
Другие функциональности и примеры
Если вы хотите глубже погрузиться в корутины и поработать с ними, то я рекомендую прочитать подробнее документацию Kotlin и в особенности посмотреть отличный гайд.
Как они работают — реализация корутин
Я не стану слишком глубоко погружаться в детали, чтобы не перегружать пост. Кроме того, в следующие несколько недель я планирую написать продолжение с более детальной информацией о реализации, вместе с примерами генерации байткода. Поэтому сейчас ограничимся простым описанием “на пальцах”.
Советы от Романа Елизарова
Не так давно мне удалось поговорить с Романом Елизаровым из JetBrains, благодаря которому во многом и появились корутины в Kotlin. Позвольте мне поделиться полученной информацией с вами:
В: Первый вопрос, который у меня возникает: когда нужно использовать корутины и есть ли такие ситуации, где все еще необходимо будет использовать потоки?
О: Корутины нужны для асинхронных задач, которые ожидают чего-либо большую часть времени. Потоки для интенсивных CPU задач.
В: Я упоминал, что фраза “легковесные потоки” звучит немного обманчиво для меня, в особенности, если учитывать то, что корутины основаны на потоках и выполняются в пуле потоков. Мне кажется, корутины больше похожи на “таск”, который выполняется, прерывается, останавливается.
О: Фраза “легковесные потоки” скорее поверхностна, корутины во многом ведут себя как потоки с точки зрения пользователей
В: Мне бы хотелось узнать о синхронизации. Если корутины во многом похожи на потоки, то тогда будет необходимо реализовывать синхронизацию общего состояния между различными корутинами.
О: Можно использовать известные паттерны для синхронизации, но все же предпочтительней вообще не иметь общего состояния при использовании корутин. Вместо этого корутины “поощряют стиль обмена через коммуникацию”.
Выводы
Корутины — очень мощный функционал, который появился в Kotlin. Пока я не познакомился с корутинами, мне казалось, что многопоточности из Java вполне достаточно.
Корутины также позволяют нам использовать различные подходы для написания конкурентного кода, каждый из которых либо уже реализован в библиотеке (kotlinx.coroutine), либо может быть легко воплощен с ее помощью.
Пересмотрите ваш подход для конкурентного программирования в Java, все эти проверяемые исключения, жестко блокирующие стратегии и огромное количество шаблонного кода. С корутинами вполне нормально писать код последовательно при помощи вызовов suspend функций, общаясь с другими корутинами, ожидая результата, отменяя корутины и т.д.
Перспективы
Все же я убежден, что корутины действительно невероятны. Конечно, время покажет — являются ли они действительно зрелыми для высоконагруженных многопоточных приложений. Может, даже многие программисты подумают и пересмотрят свои подходы к программированию. Любопытно посмотреть, что будет дальше. Ну а сейчас, корутины пока находятся в экспериментальной стадии, значит, JetBrains могут еще адаптировать их в предстоящих релизах, основываясь на отзывах сообщества, представители которого уже вовсю пробуют их в бою и пытаются адаптировать для своих задач.
Отлично! Вы дочитали до конца весь пост. Надеюсь, что вы нашли что-нибудь полезное для себя. Буду рад любому отзыву.
Что такое Корутины в Котлине?
Корутины — это отличный функционал, доступный в языке Kotlin. Я уже опробовал его и мне он очень понравился.
Цель этой статьи — помочь вам понять Корутины. Просто будьте внимательны при прочтении и у вас всё получится.
Начнем с официального определения Корутин.
Корутины — это новый способ написания асинхронного, неблокирующего кода.
Первый вопрос, возникающий при прочтении этого определения — чем Корутины отличаются от потоков?
Корутины — это облегчённые потоки. Облегчённый поток означает, что он не привязан к нативному потоку, поэтому он не требует переключения контекста на процессор, поэтому он быстрее.
Что это значит, «не привязан к нативному потоку»?
Корутины есть во многих языках программирования.
В принципе, есть два типа Корутин:
Kotlin реализует Корутины без стека — это значит, что в Корутинах нет собственного стека, поэтому они не привязываются к нативному потоку.
И Корутины, и потоки являются многозадачными. Но разница в том, что потоки управляются ОС, а Корутины пользователями.
Теперь вы можете осознанно прочитать и понять выдержку с официального сайта Kotlin:
Корутины можно представить в виде облегчённого потока. Подобно потокам, корутины могут работать параллельно, ждать друг друга и общаться. Самое большое различие заключается в том, что корутины очень дешевые, почти бесплатные: мы можем создавать их тысячами и платить очень мало с точки зрения производительности. Потоки же обходятся дорого. Тысяча потоков может стать серьезной проблемой даже для современной машины.
Давайте посмотрим, как работать с Корутинами
Итак, как запустить корутину (по аналогии с запуском потока)?
Есть две функции для запуска корутины:
launch<> vs async<> в Корутинах Kotlin
Давайте посмотрим на использование launch<>
Этот код запустит новую корутину в данном пуле потоков. В этом случае мы используем CommonPool, который использует ForkJoinPool.commonPool(). Потоки все ещё существуют в программе, основанной на корутинах, но один поток может запускать много корутин, поэтому нет необходимости в слишком большом количестве потоков.
Попробуем одну вещь:
Если вы cделаете это прямо в основной функции, то получите сообщение об ошибке:
Функции прерывания можно вызвать только из корутины или другой функции прерывания.
Функция задержки является функцией прерывания, соответственно мы можем ее вызывать только из корутины или другой функции прерывания.
Давайте исправим это:
Теперь посмотрим на использование async<>
Как использовать Котлин корутины (Kotlin Coroutines) в андроид приложении. Часть 1
Это первый курок курса о том, как использовать Котлин корутины в андроид приложении.
В этом курсе вы узнаете, как использовать Kotlin Coroutines в приложении для Android — новый способ управления фоновыми потоками (background threads), который может упростить код за счет уменьшения потребности в обратных вызовах (callbacks). Корутины, или сопрограммы — это функция Kotlin, которая преобразует асинхронные обратные вызовы для длительных задач, таких как доступ к базе данных или сети, в последовательный (sequential) код.
Чтобы на практике увидеть работу с Kotlin Coroutines и архитектурными компонентами, записывайтесь на продвинутый курс по разработке приложения «Чат-мессенжер»
Вот фрагмент кода, чтобы дать вам представление о том, что вы будете делать:
Код на основе обратного вызова будет преобразован в последовательный код с использованием корутин:
Вы начнете с существующего приложения, созданного с использованием компонентов архитектуры, которое использует стиль обратного вызова для длительных задач.
К концу этого курса у вас будет достаточно опыта для преобразования существующего API для использования корутин, и вы сможете интегрировать корутины в приложение. Вы также познакомитесь с лучшими практиками для корутин и с тем, как написать тест для кода, который использует корутины.
Что вы узнаете
Что нужно знать
Для ознакомления с синтаксисом Kotlin см. Kotlin Bootcamp for Programmers.
Для ознакомления с основами многопоточности в Android см. Guide to background processing.
Что вам понадобится
Android Studio 3.3 (можно работать с другими версиями, но некоторые вещи могут отсутствовать или выглядеть иначе).
Приступаем к настройке
Исходный код
Нажмите на кнопку, чтобы загрузить весь код для серии уроков:
… или клонируйте GitHub-репозиторий из командной строки, используя следующую команду:
Репозиторий kotlin-coroutines содержит три разных приложения:
Запуск приложения
Во-первых, давайте посмотрим, как выглядит исходный пример приложения. Следуйте этим инструкциям, чтобы открыть образец приложения в Android Studio.
Это стартовое приложение использует потоки, чтобы отобразить снэк-бар через секунду после нажатия в любом месте экрана. Попробуйте сейчас, и вы должны увидеть «Hello, from threads!» после небольшой задержки В первой части этого курса вы конвертируете это приложение для использования корутин.
Это приложение использует компоненты архитектуры для отделения кода пользовательского интерфейса в MainActivity от логики приложения в MainViewModel. Найдите минутку, чтобы ознакомиться со структурой проекта.
Добавление корутин в проект
Чтобы использовать корутины в Kotlin, необходимо включить библиотеку coroutines-core в файл build.gradle (Module: app) вашего проекта. В текущем проекте это уже сделано.
Корутины на Android доступны как базовая библиотека, а также специальные расширения для Android:
Что такое Корутины в Котлине?
Корутины — это отличный функционал, доступный в языке Kotlin. Я уже опробовал его и мне он очень понравился.
Цель этой статьи — помочь вам понять Корутины. Просто будьте внимательны при прочтении и у вас всё получится.
Начнем с официального определения Корутин.
Корутины — это новый способ написания асинхронного, неблокирующего кода.
Первый вопрос, возникающий при прочтении этого определения — чем Корутины отличаются от потоков?
Корутины — это облегчённые потоки. Облегчённый поток означает, что он не привязан к нативному потоку, поэтому он не требует переключения контекста на процессор, поэтому он быстрее.
Что это значит, «не привязан к нативному потоку»?
Корутины есть во многих языках программирования.
В принципе, есть два типа Корутин:
Kotlin реализует Корутины без стека — это значит, что в Корутинах нет собственного стека, поэтому они не привязываются к нативному потоку.
И Корутины, и потоки являются многозадачными. Но разница в том, что потоки управляются ОС, а Корутины пользователями.
Теперь вы можете осознанно прочитать и понять выдержку с официального сайта Kotlin:
Корутины можно представить в виде облегчённого потока. Подобно потокам, корутины могут работать параллельно, ждать друг друга и общаться. Самое большое различие заключается в том, что корутины очень дешевые, почти бесплатные: мы можем создавать их тысячами и платить очень мало с точки зрения производительности. Потоки же обходятся дорого. Тысяча потоков может стать серьезной проблемой даже для современной машины.
Давайте посмотрим, как работать с Корутинами
Итак, как запустить корутину (по аналогии с запуском потока)?
Есть две функции для запуска корутины:
launch<> vs async<> в Корутинах Kotlin
###Давайте посмотрим на использование launch<>
Этот код запустит новую корутину в данном пуле потоков. В этом случае мы используем CommonPool, который использует ForkJoinPool.commonPool(). Потоки все ещё существуют в программе, основанной на корутинах, но один поток может запускать много корутин, поэтому нет необходимости в слишком большом количестве потоков.
Попробуем одну вещь:
Если вы cделаете это прямо в основной функции, то получите сообщение об ошибке:
Функции прерывания можно вызвать только из корутины или другой функции прерывания.
Функция задержки является функцией прерывания, соответственно мы можем ее вызывать только из корутины или другой функции прерывания.