Корутины и асинхронность
Корутины — это лёгкие потоки выполнения Kotlin, которые позволяют писать асинхронный код в линейном, читаемом стиле через suspend-функции, не блокируя главный поток.
Суть: любая сеть и работа с базой на Android выполняется асинхронно, и корутины делают это просто — код выглядит последовательным, а под капотом не блокирует интерфейс.
Главный поток Android отвечает за отрисовку интерфейса. Если выполнить в нём долгую операцию — сетевой запрос или чтение базы, — интерфейс зависнет, и система может показать диалог «приложение не отвечает». Раньше для фоновой работы городили колбэки, и код превращался в «лестницу» вложенных вызовов. Корутины решают это: вы пишете код последовательно, а тяжёлая часть выполняется в фоне.
Функция, которая может приостанавливаться, помечается словом suspend. Вызвать её можно только из другой suspend-функции или из корутины. Приостановка не блокирует поток: пока операция идёт, поток свободен для другой работы.
import kotlinx.coroutines.*
suspend fun loadUser(): String {
delay(1000) // имитация сетевого запроса, поток не блокируется
return "Аня"
}
fun main() = runBlocking {
println("Загрузка...")
val user = loadUser() // приостановка без блокировки
println("Готово: $user")
}Диспетчеры
Диспетчер определяет, на каком пуле потоков выполняется корутина. Dispatchers.Main — главный поток для UI. Dispatchers.IO — для сети и диска. Dispatchers.Default — для тяжёлых вычислений. Современные библиотеки (Retrofit, Room) уже переключаются на нужный диспетчер сами, давая main-safety из коробки.
suspend fun fetchData(): String = withContext(Dispatchers.IO) {
// выполняется в фоновом пуле, безопасно для главного потока
// ... сетевой запрос ...
"данные"
}Как работает под капотом
Корутина — не отдельный поток ОС, а конструкция языка. Компилятор превращает suspend-функцию в конечный автомат: в точке приостановки сохраняется состояние, поток освобождается, а когда результат готов, выполнение возобновляется с того же места. Поэтому тысячи корутин могут работать на нескольких потоках. Structured concurrency связывает корутины в иерархию через CoroutineScope: если родитель отменяется (например, экран закрылся), все дочерние корутины отменяются автоматически — это предотвращает утечки.
Главный поток vs корутина Без корутин (блокировка): main: [---- сетевой запрос 1с ----] UI завис С корутиной (приостановка): main: [запуск] ... свободен для UI ... [результат] IO: [---- запрос 1с ----]
Частые ошибки
Запускать тяжёлую работу на Main. Сеть и диск на главном потоке вешают интерфейс; используйте IO-диспетчер (или доверьтесь main-safety библиотек).
Запускать корутины в GlobalScope. Такие корутины не привязаны к жизненному циклу и утекают; используйте scope, связанный с ViewModel или экраном.
Глотать отмену. Перехватывая общий Exception, можно случайно поймать отмену корутины; обрабатывайте её корректно.
Best practices
- Сеть и диск выполняйте в фоне (Dispatchers.IO) или доверяйтесь main-safety библиотек.
- Запускайте корутины в правильном scope (viewModelScope), а не в GlobalScope.
- Опирайтесь на structured concurrency: отмена родителя отменяет детей.
- Не блокируйте главный поток ничем, что занимает заметное время.
Идею «приостановка вместо блокировки» удобно прочувствовать на Python через asyncio — там та же модель кооперативной многозадачности. Запустите врезку.
# Аналог приостановки без блокировки (как suspend в Kotlin)
import asyncio
async def load_user():
await asyncio.sleep(0) # точка приостановки, поток свободен
return 'Аня'
async def main():
print('Загрузка...')
user = await load_user()
print('Готово:', user)
asyncio.run(main())Попробуй сам ▶ — добавьте параллельный запуск двух задач через asyncio.gather. В Kotlin это были бы две корутины через async/await внутри одного scope.
Закрепим главное
Удержите ключевое различие: приостановка — это не блокировка. Заблокированный поток стоит и ничего не делает; приостановленная корутина освобождает поток, и он занимается другой работой. Именно поэтому на горстке потоков уживаются тысячи корутин, и именно поэтому асинхронный код на Kotlin выглядит линейно, без лестницы колбэков. Эта модель — фундамент всей сетевой и дисковой работы в Android, и понимание её снимает страх перед асинхронностью.
Второй ориентир — structured concurrency как защита от утечек. Корутина всегда живёт в каком-то scope, и судьба scope определяет её судьбу. Привязав работу к viewModelScope, вы получаете автоматическую отмену при уничтожении экрана: незавершённый запрос не повиснет в воздухе и не попытается обновить уже мёртвый UI. Поэтому правило «никакого GlobalScope, всегда осмысленный scope» — это не педантизм, а прямая профилактика трудноуловимых багов.
Итог: корутины делают асинхронный код линейным и безопасным для интерфейса, а диспетчеры и structured concurrency управляют тем, где и как долго он живёт. На этой основе мы построим связку ViewModel и реактивного состояния.