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

Что такое Корутины в Котлине?

что такое корутины kotlin. Смотреть фото что такое корутины kotlin. Смотреть картинку что такое корутины kotlin. Картинка про что такое корутины kotlin. Фото что такое корутины kotlin

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

Цель этой статьи — помочь вам понять Корутины. Просто будьте внимательны при прочтении и у вас всё получится.

Начнем с официального определения Корутин.

Корутины — это новый способ написания асинхронного, неблокирующего кода.

Первый вопрос, возникающий при прочтении этого определения — чем Корутины отличаются от потоков?

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

Что это значит, «не привязан к нативному потоку»?

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

В принципе, есть два типа Корутин:

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

И Корутины, и потоки являются многозадачными. Но разница в том, что потоки управляются ОС, а Корутины пользователями.

Теперь вы можете осознанно прочитать и понять выдержку с официального сайта Kotlin:

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

Давайте посмотрим, как работать с Корутинами

Итак, как запустить корутину (по аналогии с запуском потока)?

Есть две функции для запуска корутины:

launch<> vs async<> в Корутинах Kotlin

Давайте посмотрим на использование launch<>

Этот код запустит новую корутину в данном пуле потоков. В этом случае мы используем CommonPool, который использует ForkJoinPool.commonPool(). Потоки все ещё существуют в программе, основанной на корутинах, но один поток может запускать много корутин, поэтому нет необходимости в слишком большом количестве потоков.

Попробуем одну вещь:

Если вы cделаете это прямо в основной функции, то получите сообщение об ошибке:

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

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

Давайте исправим это:

Теперь посмотрим на использование async<>

Источник

Корутины в Kotlin (гайд)

что такое корутины kotlin. Смотреть фото что такое корутины kotlin. Смотреть картинку что такое корутины kotlin. Картинка про что такое корутины kotlin. Фото что такое корутины 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: как работать асинхронно в Android

May 6, 2020 · 6 min read

что такое корутины kotlin. Смотреть фото что такое корутины kotlin. Смотреть картинку что такое корутины kotlin. Картинка про что такое корутины kotlin. Фото что такое корутины kotlin

Kotlin предоставляет корутины, которые помогают писать асинхронный код синхронно. Android — это однопоточная платформа, и по умолчанию все работает на основном потоке (потоке UI). Когда настает время запускать операции, несвязанные с UI (например, сетевой вызов, работа с БД, I/O операции или прием задачи в любой момент), мы распределяем задачи по различным потокам и, если нужно, передаем результат обратно в поток UI.

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

что такое корутины kotlin. Смотреть фото что такое корутины kotlin. Смотреть картинку что такое корутины kotlin. Картинка про что такое корутины kotlin. Фото что такое корутины kotlin

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

Сравним корутины с потоком

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

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

2. Потоки стоят дорого. Их создание и остановка обходится дорого, включает в себя создание собственного стека. Потоки управляются ОС. Планировщик потоков добавляет дополнительную нагрузку.

3. Потоки блокируются. Если вы выполняете такую простую задачу, как задержка выполнения на секунду (Sleep), поток будет заблокирован и не может быть использован для другой операции.

4. Потоки не знают о жизненном цикле. Они не знают о компонентах Lifecycle (Activity, Fragment, ViewModel). Поток будет работать, даже если компонент UI будет уничтожен, что требует от нас разобраться с очисткой и утечкой памяти.

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

что такое корутины kotlin. Смотреть фото что такое корутины kotlin. Смотреть картинку что такое корутины kotlin. Картинка про что такое корутины kotlin. Фото что такое корутины kotlin

Корутины легкие и супербыстрые

Пусть код скажет за себя.

Я создам 10к потоков, что вообще нереалистично, но для понимания эффекта корутин пример очень наглядный:

Здесь каждый поток ожидает 1 мс. Запуск этой функции занял около 12,6 секунд. Теперь давайте создадим 100к корутин (в 10 раз больше) и увеличим задержку до 10 секунд (в 10000 раз больше). Не волнуйтесь про “runBlocking” или “launch” (конструкторах Coroutine).

14 секунд. Сама задержка составляет 10 секунд. Это очень быстро. Создание 100 тысяч потоков может занять целую вечность.

Если вы посмотрите на метод creating_10k_Thread(), то увидите, что существует задержка в 1 мс. Во время нее он заблокирован, т.е. ничего не может делать. Вы можете создать только определенное количество потоков в зависимости от количества ядер. Допустим, возможно создать до 8 потоков в системе. В примере мы запускаем цикл на 10000 раз. Первые 8 раз будут созданы 8 потоков, который будут работать параллельно. На 9-й итерации следующий поток не сможет быть создан, пока не будет доступного. На 1 мс поток блокируется. Затем создастся новый поток и по итогу блокировка на 1мс создает задержку. Общее время блокировки для метода составит 10000/ мс. А также будет использоваться планировщик потоков, что добавит дополнительной нагрузки.

Для creatingCoroutines() мы установили задержку в 10 сек. Корутина приостанавливается, не блокируется. Пока метод ждет 10 секунд до завершения, он может взять задачу и выполнить ее после задержки. Корутины управляются пользователем, а не ОС, что делает их быстрее. В цифрах, каждый поток имеет свой собственный стек, обычно размером 1 Мбайт. 64 Кбайт — это наименьший объем пространства стека, разрешенный для каждого потока в JVM, в то время как простая корутина в Kotlin занимает всего несколько десятков байт heap памяти.

Еще пример для лучшего понимания:

что такое корутины kotlin. Смотреть фото что такое корутины kotlin. Смотреть картинку что такое корутины kotlin. Картинка про что такое корутины kotlin. Фото что такое корутины kotlin

Во фрагменте 1 мы последовательно вызываем методы fun1 и fun2 в основном потоке. На 1 секунду поток будет заблокирован. Теперь рассмотрим пример с корутиной.

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

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

Как же корутина приостанавливает свою работу?

Если вы посмотрите на выход, то увидите, что ‘completionHandler’ выполняется после завершения ‘asyncOperation’. ‘asyncOperation’ выполняется в фоновом потоке, а ‘completionHandler’ ожидает его завершения. В ‘completionHandler’ происходит обновление textview. Давайте рассмотрим байтовый код метода ‘asyncOperation’.

что такое корутины kotlin. Смотреть фото что такое корутины kotlin. Смотреть картинку что такое корутины kotlin. Картинка про что такое корутины kotlin. Фото что такое корутины kotlin

Во второй строке есть новый параметр под названием ‘continuation’, добавленный к методу asyncOperation. Continuation (продолжение) — это рабочий вариант для приостановки кода. Продолжение добавляется в качестве параметра к функции, если она имеет модификатор ‘suspend’. Также он сохраняет текущее состояние программы. Думайте о нем как о передаче остальной части кода (в данном случае метода completionHandler()) внутрь оболочки Continuation. После завершения текущей задачи выполнится блок продолжения. Поэтому каждый раз, когда вы создаете функцию suspend, вы добавляете в нее параметр продолжения, который обертывает остальную часть кода из той же корутины.

Coroutine очень хорошо работает с Livedata, Room, Retrofit и т.д. Еще один пример с корутиной:

Источник

Современный подход к конкурентности в Android: корутины в Kotlin

Напоминаем, что у нас уже открыт предзаказ на долгожданную книгу о языке Kotlin из знаменитой серии Big Nerd Ranch Guides. Сегодня мы решили предложить вашему вниманию перевод статьи, рассказывающей о корутинах Kotlin и о правильной работе с потоками в Android. Тема обсуждается очень активно, поэтому для полноты картины также рекомендуем посмотреть эту статью с Хабра и этот подробный пост из блога компании Axmor Software.

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

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

Приостановка и блокирование: в чем разница

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

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

Второй пример: теперь нам требуется, чтобы были выполнены 2 фоновые задачи, чтобы ими можно было воспользоваться. Мы применим дуэт async/await, чтобы две эти задачи выполнялись параллельно, и воспользуемся их результатом в главном потоке, как только обе задачи будут готовы:

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

launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED) немедленно выполнит свое лямбда-выражение в текущем потоке.

Dispatchers.Main гарантирует, что когда корутина возобновит работу, она будет направлена в главный поток; кроме того, Handler используется здесь как нативная реализация Android для отправки в цикл событий приложения.

Точная реализация выглядит так:

Вот хорошая статья помогающая разобраться в тонкостях диспетчеризации в Android:
Understanding Android Core: Looper, Handler, and HandlerThread.

Контекст корутины (он же – диспетчер корутины) определяет, в каком потоке будет выполняться ее код, что делать, если будет выброшено исключение, и обращается к родительскому контексту для распространения отмены.

Интерфейс coroutineScope упрощает обработку ошибок:
Если откажет какая-либо из его дочерних корутин, то откажет и вся область видимости, и все дочерние корутины будут отменены.

При работе с coroutineScope функция useValues будет вызываться лишь в случае, если извлечение обоих значений прошло успешно. Также, если deferred2 откажет, deferred1 будет отменена.

Также можно “поместить в область видимости” целый класс, чтобы задать для него контекст CoroutineContext по умолчанию и использовать его.

Пример класса, реализующего интерфейс CoroutineScope :

Запуск корутин в CoroutineScope :

Автономный запуск корутины (вне какого-либо CoroutineScope):

Можно даже определить область видимости для приложения, задав диспетчер Main по умолчанию:

Определение канала из документации JetBrains:

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

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

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

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

Жизненный цикл Android + корутины

Акторы могут очень пригодиться и для управления пользовательским интерфейсом Android, упрощают отмену задач и предотвращают перегрузку главного потока.
Давайте это реализуем и вызовем job.cancel() при уничтожении активности.

Класс SupervisorJob похож на обычный Job с тем единственным исключением, что отмена распространяется только в нисходящем направлении.

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

Упрощаем ситуацию с обратными вызовами (часть 1)

API работает вот так:

Теперь функция refresh стала понятнее. Она создает канал, вызывает обозреватель VLC, затем формирует список медиа-файлов и обрабатывает его.

Вместо функций select или consumeEach можно использовать for для ожидания медиа, и этот цикл будет разрываться, как только канал browserChannel закроется.

Упрощаем ситуацию с обратными вызовами (часть 2): Retrofit

Второй подход: мы вообще не используем корутины kotlinx, зато применяем корутинный core-фреймворк.

Смотрите, как на самом деле работают корутины!

Таким образом, вызов, блокирующий сеть, делается в выделенном потоке Retrofit, корутина находится здесь, ожидая отклика от сервера, а использовать ее в приложении – проще некуда!

Такая реализация вдохновлена библиотекой gildor/kotlin-coroutines-retrofit.

Также имеется JakeWharton/retrofit2-kotlin-coroutines-adapter с другой реализацией, дающей аналогичный результат.

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

Также можно создавать каналы при помощи функции Produce.

Источник

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

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