Что такое coroutine в Kotlin?
В Kotlin coroutine (корутина) — это лёгкий механизм для асинхронного и неблокирующего программирования. Они позволяют писать асинхронный код так же просто, как синхронный, не блокируя потоки и не создавая их лишний раз. Корутины — часть стандартной библиотеки Kotlin и активно используются для выполнения фоновых операций, например, в Android-приложениях, при работе с сетью или базами данных.
Что такое корутина технически
Корутина — это объект, который можно приостановить и возобновить в определённой точке выполнения. В отличие от потоков (threads), корутины не создают системный поток, а работают поверх существующего пула потоков, используя диспетчер (например, Dispatchers.IO, Dispatchers.Main, Dispatchers.Default).
Как запустить корутину
Запуск корутины осуществляется через специальные функции: launch, async, runBlocking и другие. Все они являются билдерами корутин.
import kotlinx.coroutines.\*
fun main() = runBlocking {
launch {
delay(1000)
println("Корутинa завершилась")
}
println("Главный поток")
}
Вывод:
Главный поток
Корутинa завершилась
В этом примере:
-
runBlocking запускает корутину и блокирует текущий поток (обычно main) до завершения вложенных корутин.
-
launch запускает новую корутину, которая выполняется параллельно, но не блокирует поток.
-
delay() — приостанавливает корутину на указанное время без блокировки потока, в отличие от Thread.sleep().
Основные понятия
CoroutineScope
Контекст, в котором выполняются корутины. У него есть Job, который управляет жизненным циклом всех запущенных в нём корутин.
val scope = CoroutineScope(Dispatchers.Default)
scope.launch {
// работа внутри этого scope
}
Job
Объект, связанный с запущенной корутиной. Через него можно отменить корутину (job.cancel()), проверить её статус и ждать её завершения (job.join()).
Deferred
Используется с async. Позволяет получить результат работы корутины, как Future в Java.
val deferred = async {
doSomething()
}
val result = deferred.await() // неблокирующее ожидание
Базовые строительные функции
Функция | Назначение |
---|---|
launch | Запускает корутину без возвращаемого результата (аналог fire-and-forget) |
--- | --- |
async | Запускает корутину с возвращаемым результатом (Deferred<T>) |
--- | --- |
runBlocking | Запускает корутину и блокирует поток до завершения |
--- | --- |
withContext | Переключает контекст выполнения корутины |
--- | --- |
Диспетчеры (Dispatchers)
Диспетчер управляет тем, где и как выполняется корутина (на каком потоке/пуле потоков):
Диспетчер | Описание |
---|---|
Dispatchers.Main | Главный поток (UI-поток) — используется в Android и UI-приложениях |
--- | --- |
Dispatchers.IO | Пул потоков для операций ввода/вывода (работа с файлами, сетью, БД) |
--- | --- |
Dispatchers.Default | Используется для CPU-интенсивных задач |
--- | --- |
Dispatchers.Unconfined | Начинается в текущем потоке, но может возобновиться в другом |
--- | --- |
Приостановка и возобновление
Корутины можно приостанавливать с помощью suspend функций — это функции, которые могут быть прерваны, а затем продолжены позже.
suspend fun getData(): String {
delay(1000)
return "данные"
}
suspend-функции можно вызывать только из других корутин или других suspend-функций.
Корутины и структурированная конкуренция
Одна из философий корутин в Kotlin — structured concurrency. Это означает, что все корутины должны быть привязаны к какому-либо CoroutineScope. Когда scope отменяется или завершается, все дочерние корутины автоматически отменяются.
fun CoroutineScope.launchWork() {
launch {
delay(1000)
println("Работа выполнена")
}
}
fun main() = runBlocking {
launchWork() // вложена в runBlocking — будет отменена при его завершении
}
Обработка ошибок
Корутины используют исключения, как обычный код. Для launch и async механизмы немного отличаются:
-
В launch ошибка пробрасывается в CoroutineExceptionHandler
-
В async ошибка сохраняется в Deferred и выбрасывается при вызове await()
Пример:
val handler = CoroutineExceptionHandler { \_, exception ->
println("Ошибка: $exception")
}
scope.launch(handler) {
throw RuntimeException("Что-то пошло не так")
}
Преимущества корутин
-
Лёгкие: можно запускать тысячи корутин, не перегружая систему
-
Ленивые: не блокируют потоки (в отличие от Thread.sleep)
-
Удобный синтаксис: выглядит как обычный последовательный код
-
Structured concurrency: помогает избежать утечек и неуправляемых задач
-
Хорошо масштабируются: подходят для высоконагруженных систем
Примеры
Параллельный запуск
val one = async { getFirst() }
val two = async { getSecond() }
val sum = one.await() + two.await()
Последовательный запуск
val a = getFirst()
val b = getSecond()
Отмена корутин
val job = launch {
repeat(1000) {
println("Работаю $it")
delay(500)
}
}
delay(2000)
job.cancelAndJoin() // отмена + ожидание завершения
Таймаут
withTimeout(3000) {
// если операция не завершится за 3 секунды — будет TimeoutCancellationException
}
Внутреннее устройство
Корутины в Kotlin реализованы на основе continuation-passing style (CPS): компилятор переписывает suspend-функции в виде состояний, а каждое приостановленное место — это точка возврата. Это позволяет возобновить выполнение с того же места после delay, await, и т.п., без создания нового потока.
Под капотом используется интерфейс Continuation<T>, хранилище состояний (StateMachine) и механизм диспетчеризации.
Это делает корутины чрезвычайно производительными и лёгкими для многозадачности.