Расскажи подробно про корутины


Корутины (coroutines) в Kotlin — это легковесный инструмент для асинхронного и неблокирующего программирования, позволяющий писать последовательный, читаемый код для выполнения фоновых операций, не блокируя основной поток. Они предоставляют удобную альтернативу колбэкам, RxJava, потокам (Thread) и позволяют эффективно управлять конкурентностью.

1. Что такое корутина

Корутина — это вычисление, которое можно приостанавливать и возобновлять без блокировки потока. То есть она может "спать", не занимая ресурсы ОС, и затем продолжить выполнение с того же места. Они создаются и управляются с помощью API, предоставляемого библиотекой kotlinx.coroutines.

Корутины интегрированы в язык Kotlin и работают как расширение стандартной библиотеки.

2. Почему корутины — это не потоки

Хотя снаружи они похожи на потоки, корутины:

  • работают в одном потоке, если явно не указано иное;

  • не создают новый поток при запуске (если не указать Dispatchers.IO, Dispatchers.Default и т. д.);

  • приостанавливаются вместо блокировки — CPU не занят;

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

3. Основные компоненты корутин

3.1. suspend функции

Ключевое слово suspend используется для обозначения функции, которая может быть приостановлена (и возобновлена позже):

suspend fun fetchData(): String {
delay(1000)
return "Готово"
}

suspend-функции могут вызываться только внутри других suspend-функций или корутинных блоков.

3.2. CoroutineScope

Определяет жизненный цикл и контекст выполнения корутин:

CoroutineScope(Dispatchers.Default).launch {
// код внутри корутины
}

3.3. launch, async, runBlocking

  • launch {} — запускает корутину "в фоне", ничего не возвращает;

  • async {} — запускает корутину и возвращает Deferred<T> (аналог Future);

  • runBlocking {} — запускает корутину и блокирует поток (например, для main()).

4. Dispatchers (диспетчеры)

CoroutineDispatcher определяет, где и как будет выполняться корутина:

Dispatcher Назначение
Dispatchers.Default Для CPU-интенсивных задач (сортировка, вычисления)
--- ---
Dispatchers.IO Для I/O операций: файловая система, сеть, базы данных
--- ---
Dispatchers.Main Для взаимодействия с UI (в Android — главный поток)
--- ---
Dispatchers.Unconfined Стартует в вызывающем потоке, затем продолжает в первом приостановлении
--- ---

Пример:

CoroutineScope(Dispatchers.IO).launch {
val data = fetchData()
withContext(Dispatchers.Main) {
textView.text = data
}
}

5. Ключевые функции и понятия

5.1. delay()

Аналог Thread.sleep(), но не блокирует поток:

delay(1000) // приостанавливает корутину на 1 секунду

5.2. withContext()

Переключает контекст выполнения:

val result = withContext(Dispatchers.IO) {
loadFromDisk()
}

5.3. supervisorScope {}

Обеспечивает, чтобы ошибки одной корутины не отменяли остальные:

supervisorScope {
launch { /\* ... \*/ }
launch { /\* ... \*/ }
}

6. Structured concurrency (структурированная конкуренция)

В Kotlin-корутинах действует принцип: все корутины должны быть привязаны к родительскому CoroutineScope. Это упрощает управление жизненным циклом:

class MyViewModel : ViewModel() {
private val viewModelScope = CoroutineScope(Dispatchers.Main + Job())
fun loadData() {
viewModelScope.launch {
// ...
}
}
}

Когда viewModelScope отменяется (например, при onCleared()), все вложенные корутины автоматически завершатся.

7. Обработка ошибок

Ошибки в корутинах можно перехватывать при помощи try-catch, а также с помощью CoroutineExceptionHandler:

val handler = CoroutineExceptionHandler { \_, exception ->
println("Ошибка: $exception")
}
CoroutineScope(Dispatchers.Main + handler).launch {
try {
throw IllegalStateException("Ошибка внутри корутины")
} catch (e: Exception) {
println("Поймали ошибку")
}
}

8. Отмена корутин

Каждая корутина может быть отменена. Для этого у неё должен быть Job, а операции внутри неё — кооперативными (например, delay() или isActive):

val job = CoroutineScope(Dispatchers.Default).launch {
while (isActive) {
// повторяющиеся действия
}
}
job.cancel()

9. Пример полной корутины в Android

class MyViewModel : ViewModel() {
fun fetch() {
viewModelScope.launch {
val data = withContext(Dispatchers.IO) {
api.loadData()
}
// обновление UI
}
}
}

Здесь:

  • запуск внутри viewModelScope;

  • withContext(IO) переключает поток;

  • launch не блокирует UI.

10. Корутины и Jetpack Compose

В Compose корутины тесно интегрированы через rememberCoroutineScope() и LaunchedEffect():

@Composable
fun MyScreen() {
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
delay(1000)
println("Нажали через секунду")
}
}) {
Text("Нажми")
}
}

11. Работа с Flow

Корутины тесно связаны с Flow — реактивным API Kotlin:

fun fetchNumbers(): Flow&lt;Int&gt; = flow {
for (i in 1..5) {
emit(i)
delay(500)
}
}

Чтение из Flow:

lifecycleScope.launch {
fetchNumbers().collect { value ->
println("Получили $value")
}
}

Корутины — один из самых мощных и выразительных инструментов в Kotlin, позволяющий писать асинхронный код так же просто, как синхронный. Они эффективны по ресурсам, интегрированы в Android Jetpack, поддерживают structured concurrency, cancellation, управление контекстами, и хорошо сочетаются с Compose и Flow.