Расскажи подробно про корутины
Корутины (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<Int> = 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.