Coroutines (корутините) са леки нишки в Kotlin за асинхронно програмиране, достъпни чрез библиотеката kotlinx.coroutines. Според JetBrains Kotlin Documentation, 2026, Coroutines позволяват спиране на изпълнението на функция без блокиране на нишката, за разлика от традиционните Thread. Корутините се изпълняват в ограничен пул от нишки, което ги прави хиляди пъти по-леки от нативните нишки. Kotlin Coroutines са напълно интегрирани с Android Jetpack, Retrofit, Room и други популярни библиотеки от екосистемата на Android.
Основни точки
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 — ключова дума в Kotlin, която маркира функция като спираема. Такава функция може да бъде извикана само от друга suspend функция или от корутина. Вътре в suspend функция могат да се извикват други suspend функции в произволен ред и всяка точка на извикване е потенциална точка на спиране.
Механиката е проста: когато suspend функция извиква друга suspend функция, тя спира в тази точка, освобождавайки нишката. След завършване на извиканата функция, runtime продължава изпълнението от запазеното място. Това се нарича cooperative cancellation — никоя нишка не се блокира.
Важно: suspend функцията не е асинхронна по подразбиране. Редът на изпълнение остава последователен, ако не се използват launch или async. suspend позволява само функцията да бъде спряна без блокиране на нишката и да бъде част от контекста на корутината. Continuation Passing Style е модел на компилация, при който всяка suspend функция получава скрит callback Continuation, а компилаторът генерира краен автомат за управление на спирането и възобновяването.
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 задачи | Не се отменя автоматично |
| viewModelScope | Android ViewModel | При почистване на ViewModel |
| lifecycleScope | Android Activity/Fragment | При унищожаване на lifecycle |
| coroutineScope | Вътре в suspend функция | При отмяна на parent job |
Обикновеният Job при грешка на една дъщерна корутина отменя всички siblings. SupervisorJob е изключение: повреда в една дъщерна корутина не засяга останалите. Това е важно, когато няколко независими задачи се изпълняват паралелно и една може да се провали, без да е необходимо да се отменят другите.
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 — само за тестове или входни точки.
Нека разгледаме три практически сценария: основна корутина с launch, паралелно извикване с async и обработка на грешки с SupervisorJob.
viewModelScope.launch стартира корутина в контекста на ViewModel. При почистване на ViewModel корутината автоматично се отменя.
class ProfileViewModel : ViewModel() {
fun loadUser() {
viewModelScope.launch(Dispatchers.IO) {
val user = api.fetchUser()
withContext(Dispatchers.Main) {
showUser(user)
}
}
}
}
coroutineScope с async стартира три заявки паралелно. Резултатите се събират чрез .await(). Ако някоя от заявките се провали, всички се отменят.
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 позволява на всяка корутина да завърши независимо. Грешка в една заявка не отменя останалите.
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 без системни извиквания.
Според Google (2025), използването на корутини вместо нишки намалява консумацията на памет за фонови задачи в Android приложения с 90–95%. Всички модерни Android библиотеки (Retrofit, Room, WorkManager) имат вградена поддръжка за корутини чрез suspend функции. Ktor (HTTP клиентският фреймуърк на JetBrains) също е изцяло изграден върху корутини, предоставяйки suspend-функции за всяка заявка без callback API. Room поддържа корутини чрез suspend-функции в DAO, което позволява изпълнение на заявки към базата данни без блокиране на основната нишка.
Нишките остават необходими за нативен код чрез JNI, блокиращи CPU интензивни извиквания без времево ограничение (рендиране на видео, симулации) и при интеграция с C библиотеки. За всичко останало — корутини.
Често задавани въпроси
Корутина — спираема единица работа, която се изпълнява на съществуваща нишка. Нишката е системен ресурс със собствен стек. Корутините са хиляди пъти по-леки от нишките и не блокират ресурси при спиране.
Dispatchers.IO е предназначен за блокиращи I/O операции (мрежа, файлове) и може да създава нови нишки при необходимост. Dispatchers.Default има пул с фиксиран размер (брой ядра на процесора) за CPU интензивни изчисления.
Job.cancel() отменя корутината и всички нейни деца. За проверка на отмяна вътре в корутината използвайте ensureActive() — хвърля CancellationException, ако корутината е отменена.
Да — чрез библиотеката kotlinx-coroutines-rx3. Тя предоставя функциите awaitSingle, awaitFirst и други за преобразуване на Observable/Single в suspend функции и обратно чрез flowable.
Flow — студен асинхронен поток от данни, аналог на RxJava Observable за корутини. Flow излъчва стойности последователно и завършва с изключение или успех. Поддържа операторите map, filter, catch и други.
Заключение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също