Что такое 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) и механизм диспетчеризации.

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