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

Решил написать о некоторых вещах, которых, по моему мнению, стоит и не стоит избегать при использовании корутин Kotlin.

Избегайте использования ненужных async/await

❌ Если вы используете функцию async и сразу же вызываете await, то вам следует прекратить это делать.

launch { val data = async(Dispatchers.Default) { /* code */ }.await() }

✅ Если вы хотите переключить контекст корутины и немедленно приостановить родительскую корутину, то withContext — это самый предпочтительный для этого способ.

launch { val data = withContext(Dispatchers.Default) { /* code */ } }

С точки зрения производительности это не такая большая проблема (даже если учесть, что async создаёт новую корутину для выполнения работы), но семантически async подразумевает, что вы хотите запустить несколько корутин в фоновом режиме и только потом ждать их.

Как организован процесс обучения

Вебинары проходят в мессенджере Slack — там же взаимодействуют преподаватели и сокурсники. Раз в две недели вам придётся делать домашнее задание, рассчитанное на 3-4 часа свободного времени. При выполнении задач слушатели всегда могут получить обратную связь от менторов. Все лекции сохраняются в личном кабинете и доступны даже после обучения. Финальный месяц курса посвящён выполнению индивидуального проекта, который станет достойной частью вашего портфолио. Самые успешные выпускники получат приглашение на собеседование в партнёрские компании — OZON, «МТС», X5 Retail Group и многие другие.

Выводы

Естественно, в одной статье невозможно раскрыть все аспекты нового инструмента в асинхронном программировании. Тем, кто заинтересовался корутинами Kotlin, можно посоветовать изучить и протестировать, к примеру, комбинирование корутин, каналы, реализацию Actor model и другие возможности.

Читайте также:  Боремся с ошибкой «Обнаружены наложения» на устройстве Андроид

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

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

Официальный сайт разработчиков Kotlin:

Анонс релиза Kotlin 1.3.0 с Coroutines: -1-3/

Коллекции

Kotlin — это независимый язык, поэтому он поставляется с чрезвычайно компактной стандартной библиотекой. Практически все API, примитивы и реализации коллекций пришли из Java. Однако благодаря функциям расширения коллекции Kotlin намного более продвинуты, поддерживают гораздо больше различных операций и имеют неизменяемые эквиваленты, такие как MutableList и List.

Одна из самых мощных особенностей коллекций Kotlin — встроенные средства функционального программирования. В Kotlin можно сделать, например, так:

val allowed = getPeople() .filter { it.age >= 21 } .map { it.name } .take(5)

Этот код возьмет список объектов People, возвращенный getPeople(), выбросит из него все объекты, значение поля age которых меньше 21, затем преобразует этот список в список строк, содержащий имена, и вернет первые пять строк.

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

Коллекции

Поэтому в Kotlin также существует Sequence — ленивый аналог Iterable:

Читайте также:  Почему я никогда не куплю смартфон на Android

val allowed = getPeople().asSequence() .filter { it.age >= 21 } .map { it.name } .take(5) .toList()

В отличие от предыдущего, этот код будет обрабатывать каждый элемент в списке отдельно, пока не будет пять элементов. Теоретически это должно серьезно улучшить производительность коллекции, и IDEA / AndroidStudio даже предлагает преобразовать коллекцию в Sequence.

В реальности же все несколько сложнее. Разработчик AJ Alt провел собственные тесты прозводительности, используя следующий код:

list.filter { true }.map { it }.filter { true }.map { it }

И код, использующий Sequence:

list.asSequence() .filter { true }.map { it }.filter { true }.map { it } .toList()

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

Есть, однако, два случая, в которых последовательности выигрывают с очень большим отрывом: методы find и first. Происходит так потому, что поиск для последовательностей останавливается после того, как нужный элемент найден, но продолжается для списков.

Коллекции

Бесполезные оптимизации

За время существования Kotlin в интернете появилось большое количество утверждений о производительности языка и советов по оптимизации

  1. Лямбды Kotlin быстрее своего аналога в Java 8 на 30% (вне зависимости от использования ключевого слова inline).
  2. Объекты-компаньоны (companion object) не создают никакого оверхеда, а доступ к ним даже быстрее, чем к статическим полям и методам в Java.
  3. Вложенные функции не создают оверхеда, они даже немного быстрее обычных.
  4. Повсеместные проверки на null не создают оверхеда, код получается более быстрый, чем код на Java.
  5. Передача массива как аргумента функции, ожидающей неопределенное число аргументов (vararg), действительно замедляет исполнение кода в два раза.
  6. Делегаты работают на 10% медленней своей наиболее эффективной эмуляции на языке Java.
  7. Скорость прохода по диапазону (range) не зависит от того, вынесен он в отдельную переменную или нет.
  8. Вынесенный в константу range всего на 3% быстрее аналога в коде.
  9. Конструкция for (it in ) { … } в три раза быстрее конструкции ().forEach { … }.
Читайте также:  Realme: Как вручную загрузить и обновить прошивку