Coroutine Builder — функции на Kotlin Coroutines, които създават и стартират корутини, определяйки начина им на изпълнение. Билдерите launch, async, runBlocking и produce покриват различни сценарии: от фонови задачи до паралелни изчисления с връщане на резултат. Според данните на JetBrains, 2024, Coroutine Builder е основата на модела на корутините, осигурявайки структурирана конкурентност и управление на жизнения цикъл.
Основни точки
Coroutine Builder — е разширяваща функция на Kotlin, която приема CoroutineScope и suspend-блок, създавайки и стартирайки нова корутина. Всеки билдер определя как корутината ще бъде изпълнена: с или без връщане на резултат, с блокиране на нишката или асинхронно. Билдерите са входни точки в модела на корутините на езика.
Всички билдери работят чрез CoroutineScope, който управлява жизнения цикъл на дъщерните корутини. При отмяна на scope всички корутини, стартирани чрез него, се отменят автоматично — това е принципът на структурираната конкурентност. Този подход предотвратява изтичане на корутини и гарантира предвидимо завършване.
import kotlinx.coroutines.*
fun main() = runBlocking {
// Билдерите работят вътре в CoroutineScope
val job = launch {
delay(1000L)
println("Свят!")
}
println("Здравей,")
job.join()
}
Kotlin предоставя четири вградени билдера на корутини: launch, async, runBlocking и produce. Всеки от тях има свой тип връщане и област на приложение. За мобилно разработване на Android основните са launch и async — те работят неблокиращо и се интегрират с архитектурните компоненти.
| Билдер | Върнат тип | Блокиране на нишка | Сценарий |
|---|---|---|---|
| launch | Job | Не | Fire-and-forget задачи |
| async | Deferred<T> | Не | Паралелни изчисления |
| runBlocking | T | Да | Тестове, main функция |
| produce | ReceiveChannel<E> | Не | Поточно предаване (deprecated) |
Всеки билдер приема допълнителни параметри: CoroutineStart (стратегия на стартиране), CoroutineContext (диспечер, изключения) и именуван блок код. По подразбиране корутината се стартира незабавно (CoroutineStart.DEFAULT).
launch — най-често използваният билдер в Android разработката. Той стартира корутина, която не връща резултат, и връща обект Job за управление на нейния жизнен цикъл. Това е идеалният избор за операции, където е необходим само страничен ефект: запис в база данни, изпращане на аналитика, обновяване на UI.
Билдерът launch приема CoroutineScope, опционален CoroutineContext и suspend-блок. Върнатият Job позволява отмяна на корутината, изчакване на нейното завършване или проверка на статуса.
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
val job: Job = scope.launch(CoroutineStart.LAZY) {
val data = fetchFromNetwork()
saveToDatabase(data)
}
job.start()
job.join()
Параметърът CoroutineStart.LAZY отлага стартирането до изрично извикване на start() или join(). Това е полезно за отложена инициализация и условно стартиране. За стандартно незабавно стартиране се използва CoroutineStart.DEFAULT или параметърът се пропуска.
async — билдер, който връща Deferred<T> — асинхронно обещание за резултат. Извикването на await() спира корутината до получаване на резултата, без да блокира нишката. Това е основният механизъм за паралелни задачи в корутините на Kotlin.
async е особено ефективен, когато трябва да се изпълнят няколко независими операции едновременно. За разлика от последователното извикване на suspend функции, async стартира корутините паралелно, съкращавайки общото време за изпълнение.
suspend fun fetchUserData(): UserData {
val deferred1 = CoroutineScope(Dispatchers.IO).async { api.getProfile() }
val deferred2 = CoroutineScope(Dispatchers.IO).async { api.getSettings() }
val deferred3 = CoroutineScope(Dispatchers.IO).async { api.getNotifications() }
return UserData(
profile = deferred1.await(),
settings = deferred2.await(),
notifications = deferred3.await()
)
}
Deferred наследява от Job, така че async поддържа всички операции от жизнения цикъл: отмяна, изчакване на завършване, обработка на изключения. При отмяна на scope дъщерните Deferred корутини се отменят автоматично.
runBlocking — единственият билдер, който блокира текущата нишка до завършване на корутината. Той създава нов CoroutineScope и стартира подадената корутина, блокирайки извикващата нишка. Използва се във входни точки main(), в тестове и при интеграция с blocking код.
runBlocking е оправдан в три сценария: входна точка на приложението (main),单元 тестове на suspend функции и интеграция с callback-базирани библиотеки, където suspend не може да се използва. В production код на Android използването на runBlocking на главната нишка категорично не се препоръчва.
class CoroutineTest {
@Test
fun `test suspend function`() = runBlocking {
val result = mySuspendFunction()
assertEquals("expected", result)
}
}
За тестове се препоръчва използването на kotlinx-coroutines-test с TestCoroutineDispatcher вместо runBlocking — това осигурява контрол на времето и избягва блокирания в тестовата среда.
Изборът на Coroutine Builder зависи от върнатия резултат и сценария на изпълнение. Ако операцията не изисква връщане на данни — използвайте launch. Ако е необходим резултат от асинхронна операция — async. runBlocking прилагайте само за bridging, а produce заменете с Flow за реактивни потоци.
В Android проекти, използващи Kotlin Coroutines, основната двойка билдери са launch и async. launch се използва в ViewModel и UseCases за стартиране на корутини, а async — за паралелни заявки към мрежа или база данни. Съвременните библиотеки (Ktor, Room) вече поддържат suspend функции, което минимизира необходимостта от директно използване на async.
Често задавани въпроси
launch връща Job и не връща резултат от изпълнението, докато async връща Deferred<T> — обект, от който може да се получи резултат чрез await(). launch се използва за fire-and-forget операции, async — за задачи, връщащи данни.
Не се препоръчва. runBlocking на главната нишка причинява ANR и блокира UI. Използвайте lifecycleScope.launch вътре в Activity и Fragment — това е вградено решение без блокирания.
Билдерът launch връща обект Job, който позволява контролиране на жизнения цикъл на корутината: отмяна (cancel), изчакване на завършване (join), проверка на статус (isActive, isCompleted, isCancelled).
Deferred<T> — е асинхронно обещание за резултат, връщано от билдера async. Наследява от Job и добавя методи await() за получаване на резултат, getCompleted() за неблокиращ достъп и getCompletionExceptionOrNull() за проверка на изключение.
Използвайте параметъра CoroutineStart.LAZY: scope.launch(start = CoroutineStart.LAZY) { ... }. След това извикайте job.start() или job.join() за действително стартиране. Това е полезно за отложена инициализация и условно изпълнение на корутини.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също