Android studio thread kotlin

Kotlin Coroutines — Thread.sleep() vs delay

Nov 19, 2018 · 3 min read

F rom the past few days, I have been trying to understand Coroutines and to be honest I have struggled a lot. For those who don’t know about Coroutines, in a nutshell, they are lightweight threads. I am not going to answer questions like what are Coroutines, why Coroutines etc. etc. since there are plenty of good articles related to that out there!. Rather I would be discussing something related to suspending functions.

One of the things I found difficult to wrap my head around are suspending functions. So what are they?

Kotlin has a suspend keyword which is its way of telling that this particular function is going to take some time for execution, maybe 10 seconds or even minutes who knows!. When such a function is called from a coroutine, instead of blocking until that function returns like a normal function call, it is suspended. What that means is that the Coroutine unblocks the thread that it’s running on while it waits for the result. During that time the thread is free to perform other task like executing another coroutine. As soon as the result is obtained, execution starts from where it was left.

Note : suspending functions can be called from another suspending functions or coroutines only. In case you try to call it from a normal function you will get an error.

Notice th e arrow on line 34 it’s the IDE telling us that this is where the suspending occurs. This is the suspending point. Since delay is a suspending function which is called from another function, the enclosing function also has the suspend keyword in its declaration.

Now that we know enough, let’s see the difference between delay and Thread.sleep() using a very simple example because

Simplicity is the glory of expression — Walt Whitman

Step 1: Add these dependencies to your build.gradle file

Step 2: Create a new activity and add this to your layout file

Step 3: Add this to your MainActivity

Step 4: Run the app, click the button and check your Logcat.

Output: You will notice that first “launched coroutine 1” is printed then after 5 seconds “Here after a delay of 5 seconds” and then finally “launched coroutine 2” is printed.

Step 5: Update your MainActivity with the following code

Step 6: Run the app, click the button and check your Logcat again.

Output: Now you will see that first “launched coroutine 1” is printed, after that “launched coroutine 2” and when 5 seconds are over “Here after a delay of 5 seconds”.

Conclusion

Since delay is a suspending function, call to delay results in non-blocking suspension thereby allowing the other Coroutine to execute. After 5 seconds when delay’s execution is finished we continue the execution of Coroutine from the point we left.

In the case of Thread.sleep() since it is a blocking call, the Coroutine is blocked for 5 seconds and only when it is done executing the other Coroutine gets a chance to run.

Voilà! This was indeed a short one, but hopefully, it gave you a better understanding of some basic concepts of Coroutines.

Источник

Корутины Kotlin: как работать асинхронно в Android

May 6, 2020 · 6 min read

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

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

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

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

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

Читайте также:  Tomb raider для андроида

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

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

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

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

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

Отметим , что это не просто другой способ асинхронного программирования , это другая парадигма.

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

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

Я создам 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 памяти.

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

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

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

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

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

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

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

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

Источник

Русские Блоги

Основы работы с потоками Kotlin

1. Создайте тему

В kotlin есть три способа создания потоков
1. Наследуйте класс Thread.

2. Используйте класс Runnable для инициализации объекта Thread.

Здесь мы не увидели объекта Runnable. Фактически его заменили лямбда-выражения.

Читайте также:  Гитар про для андроид полная версия

3. Используйте метод упаковки, предоставленный Kotlin.

Исходный код метода потока на самом деле очень прост, и в нем не так много логики.

2. Блокировка резьбы

В Java ключевое слово synchronized обычно используется для блокировки методов, но в Kotlin такого ключевого слова нет. Вместо этого используется аннотация @Synchronized.

Если нужно заблокировать блок кода, используйте метод synchronized ()

3.wait(), notify() and notifyAll()

Базовым классом Kotlin является Any, который похож на Object в Java, но не предоставляет методы wait (), notify (), notifyAll (). Но мы по-прежнему можем вызывать методы wait (), notify (), notifyAll (), создав экземпляр Object.

4. Сторонние библиотеки, связанные с потоками

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

2.Quasar library
В настоящее время нет исследований, если вам интересно, вы можете посмотреть сами, облегченный поток и реализацию сопрограммы

5. Когда встроенные встречи синхронизируются

Здесь есть некоторые проблемы: когда встроенный измененный метод добавлен к аннотации @Synchronized, механизм синхронизированной блокировки исчезнет при вызове встроенного метода.

Вы можете видеть, что после встраивания synchronized исчезает

Источник

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

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

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

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

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

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

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

Для начала возьмем самый простой пример: запустим корутину в контексте Main (главный поток). В нем мы извлечем изображение из потока IO и отправим это изображение на обработку обратно в Main .

Код прост как однопоточная функция. Причем, пока getImage выполняется в выделенном пуле потоков IO , главный поток свободен и может взяться за любую другую задачу! Функция withContext приостанавливает текущую корутину, пока работает ее действие ( getImage() ). Как только getImage() возвратится и looper из главного потока станет доступен, корутина возобновит работу в главном потоке и вызовет imageView.setImageBitmap(image) .

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

async подобен launch , но возвращает deferred (сущность Kotlin, эквивалентная Future ), поэтому ее результат можно получить при помощи await() . При вызове без параметров она работает в контексте, задаваемом по умолчанию для текущей области видимости.

Опять же, главный поток остается свободен, пока мы дожидаемся наших 2 значений.
Как видите, функция launch возвращает Job , который можно использовать для ожидания, пока операция завершится – это делается при помощи функции join() . Она работает как и в любом другом языке, с той оговоркой, что просто приостанавливает корутину, а не блокирует поток.

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

Рассмотрим, как в java выглядит эквивалент для диспетчеризации в Main , то есть,

Реализация контекста Main для Android – это диспетчер на основе Handler . Итак, это действительно очень подходящая реализация:

launch(Dispatchers.Main) посылает Runnable в Handler , так что его код выполняется не сразу.

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

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

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

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

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

job.cancel() отменит все корутины, родителем которых является job . A exceptionHandler получит все исключения, выброшенные в этих корутинах.

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

В примере async , если извлечь значение не удалось, а другая задача при этом продолжила работу – у нас возникает поврежденное состояние, и с этим надо что-то делать.

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

Читайте также:  Стрелялки для андроид гугл

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

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

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

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

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

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

  • Корутины ограничивают интероперабельность с Java
  • Ограничивают изменяемость во избежание блокировок
  • Корутины предназначены для ожидания, а не для организации потоков
  • Избегайте I/O в Dispatchers.Default (и Main …) — для этого предназначен Dispatchers.IO
  • Потоки ресурсозатратны, поэтому используются однопоточные контексты
  • Dispatchers.Default основан на ForkJoinPool , появившемся в Android 5+
  • Корутины можно использовать посредством каналов

Избавляемся от блокировок и обратных вызовов при помощи каналов

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

Канал Channel концептуально очень похож на BlockingQueue . Ключевое отличие заключается в том, что он не блокирует операцию put, он предусматривает приостанавливающий send (или неблокирующий offer ), а вместо блокирования операции take предусматривает приостанавливающий receive .

Рассмотрим простой инструмент для работы с каналами: Actor .

Actor , опять же, очень похож на Handler : мы определяем контекст корутины (то, есть, поток, в котором собираемся выполнять действия) и работаем с ним в последовательном порядке.

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

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

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

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

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

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

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

Поэтому мы не отменяем всех корутин в Activity , когда одна из них отказывает.

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

Теперь мы можем все это скомбинировать, функция setOnClick создает объединенный actor для управления ее действиями onClick . В случае множественных нажатий промежуточные действия будут игнорироваться, исключая таким образом ошибки ANR (приложение не отвечает), и эти действия будут выполняться в области видимости Activity . Поэтому при уничтожении активности все это будет отменено.

В данном примере мы задаем для Channel значение Conflated , чтобы он игнорировал часть событий, если их будет слишком много. Можно заменить его на Channel.UNLIMITED , если вы предпочитаете ставить события в очередь, не теряя ни одного из них, но все равно хотите защитить приложение от ошибки ANR.

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

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

Вот как можно преобразить использование API, основанного на обратных вызовах, благодаря Channel .

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

  1. requestBrowsing(url, listener) инициирует синтаксический разбор папки, расположенной по адресу url .
  2. Слушатель listener получает onMediaAdded(media: Media) для любого медиа-файла, обнаруженного в этой папке.
  3. listener.onBrowseEnd() вызывается по завершении синтаксического разбора папки

Вот старая функция refresh в поставщике контента для обозревателя VLC:

Создаем канал, который будет запускаться в refresh . Теперь обратные вызовы обозревателя будут лишь направлять медиа в этот канал, а затем закрывать его.

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

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

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

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

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

Функция retrofitSuspendCall оборачивает запрос на вызов Retrofit Call , чтобы сделать из него функцию suspend .

При помощи suspendCoroutine мы вызываем метод Call.enqueue и приостанавливаем корутину. Предоставленный таким образом обратный вызов обратится к continuation.resume(response) , чтобы возобновить корутину откликом от сервера, как только тот будет получен.

Далее нам остается просто объединить наши функции Retrofit в retrofitSuspendCall , чтобы с их помощью возвращать результаты запросов.

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

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

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

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

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

Наконец, при помощи каналов удобно организовать коммуникацию между компонентами UI: адаптер может передавать события нажатий в свой фрагмент/активность через Channel или, например, через Actor .

Источник

Оцените статью