Coroutines — ключови понятия, Job и Dispatchers в Kotlin

Автор: IT Sectr Публикувано: 2026-03-16 Време за четене: 8 мин

Coroutines (корутините) са леки нишки в Kotlin за асинхронно програмиране, достъпни чрез библиотеката kotlinx.coroutines. Според JetBrains Kotlin Documentation, 2026, Coroutines позволяват спиране на изпълнението на функция без блокиране на нишката, за разлика от традиционните Thread. Корутините се изпълняват в ограничен пул от нишки, което ги прави хиляди пъти по-леки от нативните нишки. Kotlin Coroutines са напълно интегрирани с Android Jetpack, Retrofit, Room и други популярни библиотеки от екосистемата на Android.

Основни точки

  • Coroutines — леки нишки в Kotlin за асинхронен код без блокиране
  • Suspend функция — функция, способна да се спира и възобновява без блокиране на нишката
  • Dispatcher определя пула от нишки за изпълнение на корутината
  • Job — дескриптор на корутина с поддръжка на отмяна и проследяване на състоянието
  • CoroutineScope управлява жизнения цикъл на корутините и отмяната им при завършване

Какво представляват корутините в Kotlin

Coroutines (корутините) са механизъм за асинхронно програмиране в Kotlin, имплементиран в библиотеката kotlinx.coroutines. За разлика от нишките на операционната система, корутините не са обвързани с конкретна нишка: те могат да спрат на една нишка и да се възобновят на друга. Една нишка може да изпълнява хиляди корутини, превключвайки между тях с минимално допълнително натоварване.

Корутините се появиха в Kotlin 1.3 (2018) като експериментална функция и станаха стабилни в Kotlin 1.5 (2021). Coroutines решават проблема callback hell подобно на async/await, но предлагат по-богато API: канали (Channel), Flow, обработка на изключения в йерархията Job и директна интеграция с Android Lifecycle.

Според JetBrains (2025), всяка корутина консумира приблизително 100 байта памет срещу 1+ MB за нативна нишка. Това позволява стартиране на милиони корутини в едно приложение без риск от OutOfMemoryError. Именно лекотата на корутините ги прави предпочитания инструмент за асинхронност в Android.

Как работят корутините под капака

Всяка корутина в Kotlin се компилира до state machine чрез Continuation Passing Style (CPS). Компилаторът добавя скрит параметър Continuation към всяка suspend функция. Continuation съдържа точката на възобновяване и всички локални променливи. Когато корутината спре, runtime запазва Continuation, а когато се възобнови, го възстановява на всяка налична нишка от пула на Dispatcher.

Suspend функции: спиране и възобновяване

suspend — ключова дума в Kotlin, която маркира функция като спираема. Такава функция може да бъде извикана само от друга suspend функция или от корутина. Вътре в suspend функция могат да се извикват други suspend функции в произволен ред и всяка точка на извикване е потенциална точка на спиране.

Механиката е проста: когато suspend функция извиква друга suspend функция, тя спира в тази точка, освобождавайки нишката. След завършване на извиканата функция, runtime продължава изпълнението от запазеното място. Това се нарича cooperative cancellation — никоя нишка не се блокира.

  • Спиране — корутината освобождава нишката, без да я блокира
  • Възобновяване — корутината продължава от същото място, където е спряла
  • Нишка — корутината може да спре на нишка A и да се възобнови на нишка B
  • Изключения — обработват се чрез try/catch точно както в синхронен код

Важно: suspend функцията не е асинхронна по подразбиране. Редът на изпълнение остава последователен, ако не се използват launch или async. suspend позволява само функцията да бъде спряна без блокиране на нишката и да бъде част от контекста на корутината. Continuation Passing Style е модел на компилация, при който всяка suspend функция получава скрит callback Continuation, а компилаторът генерира краен автомат за управление на спирането и възобновяването.

CoroutineScope и Structured Concurrency

CoroutineScope е контекст, който определя жизнения цикъл на корутините. Всички корутини трябва да бъдат стартирани вътре в scope. Когато scope се отмени (например при завършване на Activity), всички негови дъщерни корутини се отменят автоматично. Това предотвратява изтичане на фонови задачи. Android Jetpack предоставя готови scopes за всеки компонент: viewModelScope за ViewModel и lifecycleScope за Activity и Fragment, които автоматично се отменят при унищожаване на съответния компонент.

Structured Concurrency — принцип, който гарантира, че корутина няма да завърши, докато не завършат всички нейни дъщерни корутини. Йерархията на Job образува дърво: кореновата корутина създава parent job, дъщерните — child jobs. Отмяната на parent job се разпространява до всички деца. Structured Concurrency е фундаменталната разлика между корутините и нишките.

ScopeКъде се използваОтмяна
GlobalScopeСамо за daemon задачиНе се отменя автоматично
viewModelScopeAndroid ViewModelПри почистване на ViewModel
lifecycleScopeAndroid Activity/FragmentПри унищожаване на lifecycle
coroutineScopeВътре в suspend функцияПри отмяна на parent job

SupervisorJob за обработка на грешки

Обикновеният Job при грешка на една дъщерна корутина отменя всички siblings. SupervisorJob е изключение: повреда в една дъщерна корутина не засяга останалите. Това е важно, когато няколко независими задачи се изпълняват паралелно и една може да се провали, без да е необходимо да се отменят другите.

Dispatchers и строители на корутини

Dispatchers определят на кои нишки се изпълняват корутините. Dispatchers.Main — основната UI нишка на Android. Dispatchers.IO — пул за блокиращи операции (мрежа, диск). Dispatchers.Default — за CPU интензивни задачи. Dispatchers.Unconfined — стартира в текущата нишка, но не гарантира оставане на нея. Изборът на правилния Dispatcher е критичен за производителността: IO задача на Default ще блокира пула за изчисления, а CPU задача на IO ще създаде допълнителни нишки.

withContext — функция за превключване на Dispatcher вътре в корутината. Например, suspend функция, която парсира JSON, може да превключи на Dispatchers.Default по време на изчислението и да се върне на Dispatchers.Main за актуализация на UI. withContext е най-използваният строител в Android разработката.

Три основни строителя на корутини

launch — стартира корутина, връща Job, не връща резултат (fire-and-forget). async — стартира корутина, връща Deferred, от който може да се получи резултат чрез await. runBlocking — блокира текущата нишка за изпълнение на корутината (само за тестове и main функции). Избор на строител зависи от сценария: launch е подходящ за събития и актуализации, async — за задачи с резултат, runBlocking — само за тестове или входни точки.

Примери за код с корутини в Kotlin

Нека разгледаме три практически сценария: основна корутина с launch, паралелно извикване с async и обработка на грешки с SupervisorJob.

Стартиране на корутина с launch

viewModelScope.launch стартира корутина в контекста на ViewModel. При почистване на ViewModel корутината автоматично се отменя.

kotlin
class ProfileViewModel : ViewModel() {
    fun loadUser() {
        viewModelScope.launch(Dispatchers.IO) {
            val user = api.fetchUser()
            withContext(Dispatchers.Main) {
                showUser(user)
            }
        }
    }
}

Паралелни заявки с async

coroutineScope с async стартира три заявки паралелно. Резултатите се събират чрез .await(). Ако някоя от заявките се провали, всички се отменят.

kotlin
suspend fun loadDashboard(): Dashboard = coroutineScope {
    val user = async { api.fetchUser() }
    val posts = async { api.fetchPosts() }
    val stats = async { api.fetchStats() }
    Dashboard(user.await(), posts.await(), stats.await())
}

Обработка на грешки с SupervisorJob

SupervisorJob позволява на всяка корутина да завърши независимо. Грешка в една заявка не отменя останалите.

kotlin
val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
scope.launch {
    try { api.fetchUsers() } catch (e: Exception) { log(e) }
}
scope.launch {
    try { api.fetchPosts() } catch (e: Exception) { log(e) }
}

Корутини срещу нишки: сравнение и сценарии

Нишките (Thread) — примитив на операционната система. Всяка нишка има собствен стек (~1 MB) и изисква системно извикване за създаване и превключване. Корутините — езиков примитив, необвързан с ОС. Те използват Continuation за запазване на състоянието и превключват на ниво runtime без системни извиквания.

  • Памет — нишка ~1 MB, корутина ~100 байта. Разлика от 10 000 пъти
  • Създаване — нишка ~1 µs syscall, корутина ~0.01 µs на ниво JVM
  • Превключване — нишка ~0.1 µs (syscall), корутина ~0.001 µs (continuation)
  • Максимум — хиляди нишки срещу милиони корутини на устройство
  • Отмяна — нишката не може да бъде отменена отвън (премахнат Thread.stop), корутината може чрез Job.cancel()

Според Google (2025), използването на корутини вместо нишки намалява консумацията на памет за фонови задачи в Android приложения с 90–95%. Всички модерни Android библиотеки (Retrofit, Room, WorkManager) имат вградена поддръжка за корутини чрез suspend функции. Ktor (HTTP клиентският фреймуърк на JetBrains) също е изцяло изграден върху корутини, предоставяйки suspend-функции за всяка заявка без callback API. Room поддържа корутини чрез suspend-функции в DAO, което позволява изпълнение на заявки към базата данни без блокиране на основната нишка.

Кога да използвате нишки вместо корутини

Нишките остават необходими за нативен код чрез JNI, блокиращи CPU интензивни извиквания без времево ограничение (рендиране на видео, симулации) и при интеграция с C библиотеки. За всичко останало — корутини.

Често задавани въпроси

Каква е разликата между корутина и нишка?

Корутина — спираема единица работа, която се изпълнява на съществуваща нишка. Нишката е системен ресурс със собствен стек. Корутините са хиляди пъти по-леки от нишките и не блокират ресурси при спиране.

Какво е Dispatchers.IO и с какво се различава от Default?

Dispatchers.IO е предназначен за блокиращи I/O операции (мрежа, файлове) и може да създава нови нишки при необходимост. Dispatchers.Default има пул с фиксиран размер (брой ядра на процесора) за CPU интензивни изчисления.

Как да отменя стартирана корутина?

Job.cancel() отменя корутината и всички нейни деца. За проверка на отмяна вътре в корутината използвайте ensureActive() — хвърля CancellationException, ако корутината е отменена.

Могат ли да се използват корутини с RxJava?

Да — чрез библиотеката kotlinx-coroutines-rx3. Тя предоставя функциите awaitSingle, awaitFirst и други за преобразуване на Observable/Single в suspend функции и обратно чрез flowable.

Какво е Flow в корутините?

Flow — студен асинхронен поток от данни, аналог на RxJava Observable за корутини. Flow излъчва стойности последователно и завършва с изключение или успех. Поддържа операторите map, filter, catch и други.

Заключение

  • Coroutines — леки нишки в Kotlin със спиране без блокиране чрез Continuation Passing Style
  • suspend — ключова дума за маркиране на спираеми функции
  • Dispatchers управляват пула от нишки: Main, IO, Default съответно
  • CoroutineScope свързва жизнения цикъл на корутините с компонента (Activity, ViewModel)
  • launch стартира корутина без резултат, async/await — с резултат
  • Structured Concurrency гарантира йерархична отмяна на дъщерните корутини
  • Корутини срещу нишки — корутините са 10 000 пъти по-леки и са стандарт за Android

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също