Освой Kotlin играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

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

В стандартном приложении у вас есть основной поток, в котором мы взаимодействуем с компонентами (кнопки, картинки). Если основной поток заблокирован, то приложение замирает и раздражает пользователя. Особенно ярко проблема выражается, когда приложение требует данных из интернета - это узкое горлышко для приложения. Вам приходится ждать ответа от сервера и приложение вынуждено простаивать.

Корутины предназначены для асинхронного программирования и позволяют отказаться от методов обратного вызова (Callback) и RxJava. С помощью корутин Kotlin можно выполнять как конкурентные, так и параллельные вычисления.

Самый простой пример применения - вам нужно загрузить картинку с котиком с сервера и показать её в Image/ImageView - это две строчки кода. Проблема в том, что на загрузку требуется время и вторая строчка кода должна выполниться не сразу, а после выполнения первой строчки. Все операции выполняются в одном главном потоке.

Main Thread

В таком виде приложение может работать, но есть одна проблема. В течение длительного времени (3 секунды) пользователь не способен никак взаимодействовать с приложением, и нам нужно убрать это ожидание ответа с главного потока, чтобы пользователь дальше смог продолжить работать с вашей программой.

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

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

Создавая новый поток, вы запускаете код в нём параллельно вашему основному потоку.

New Thread

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

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

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

Чтобы показать преимущество корутин перед потоками, разработчики из JetBrains приводили следующий пример - Запустим 10 тысяч корутин. Система без труда справится с этой задачей. С таким же количеством потоков программа закроется от нехватки памяти.


(1..10000).forEach {
    GlobalScope.launch {
        val threadName = Thread.currentThread().name
        println("$it printed on thread ${threadName}")
    }
}
Thread.sleep(1000)

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

Код с корутинами будет выглядеть в общем виде следующим образом.


launch(Dispatchers.Main) {
    val image = withContext(Dispatchers.IO) { getImage() } // получить из контекста IO
    imageView.setImageBitmap(image) // Возвращаемся в главный поток
}

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

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

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

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

Очень сложно! До свидания!

Дополнительные материалы

Часть вторая. Запуск корутины, повтор, suspend, Job

CoroutineContext, runBlocking, Actor

Scope (область видимости)

Dispatchers/Диспетчеры

Channel

Flow

SharedFlow

StateFlow

async/await

Получить код веб-страницы при помощи корутин

Получить картинку из интернета при помощи корутин

Реклама