runBlocking — Coroutine Builder у Kotlin-у који блокира тренутну нит до завршетка прослеђене корутине. За разлику од launch и async, он није suspend-функција и може се позвати из обичног (blocking) кода. Према документацији JetBrains, 2024, runBlocking служи као мост између синхроног и асинхроног света, омогућавајући покретање корутина из main-функције и тестова.
Главно
runBlocking — је функција Kotlin-а која ствара нови CoroutineScope и покреће прослеђену корутину, блокирајући тренутну нит до њеног потпуног завршетка. За разлику од свих осталих Coroutine Builder-а, runBlocking није suspend-функција и може се позвати из обичног синхроног кода. Сигнатура runBlocking прима CoroutineContext и suspend-блок, враћајући резултат типа T.
public fun <T> runBlocking(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> T
): T
runBlocking покреће нови event-loop у тренутној нити. Када корутина позове suspend-функцију (нпр. delay() или await()), runBlocking блокира нит и извршава друге заказане корутине у истој нити до наставка суспендоване. Ово је кооперативно блокирање — нит не мирује, већ обрађује друге корутине.
Унутрашњи механизам runBlocking се заснива на event-loop-у: при позиву suspend-функције runBlocking суспендује извршење тренутног блока и покреће друге корутине из реда. Када се suspend-функција заврши, извршење се наставља. Овај циклус се наставља док се све корутине не заврше.
runBlocking користи сопствени једнонитни пул за извршавање корутина. За разлику од Dispatchers.IO или Default, runBlocking не пребацује нити — обрађује све корутине у тренутној нити, преплићући њихово извршење. Ово је једини билдер који гарантује извршење у истој нити.
fun main() {
val threadName = Thread.currentThread().getName()
println("Пре runBlocking на $threadName")
val result = runBlocking {
println("Унутар runBlocking на ${Thread.currentThread().getName()}")
delay(500L)
"Done"
}
println("Након runBlocking: $result")
}
Излаз ће показати да се сва три println извршавају на једној нити. runBlocking не пребацује нит, већ организује кооперативно мултизадавање унутар једне нити помоћу event-loop-а.
runBlocking је оправдан у три сценарија: тачка улаза main() у конзолним апликацијама, јединични тестови suspend-функција и премошћавање — позив suspend-кода из callback-базираних или blocking библиотека. У production коду Android-а употреба на главној нити је категорички забрањена.
| Сценарио | Примењивост | Ризик |
|---|---|---|
| main() конзолне апликације | Да | Нема — ово је тачка улаза, нит не блокира UI |
| JUnit тестови | Да | Минимални — тестови су по дефиницији синхрони |
| Android UI-нит | Не | ANR, кашњења, замрзавање интерфејса |
| Callback → Coroutine | Да, с опрезом | Блокирање пула нити при дуготрајним операцијама |
За Android тестове користите kotlinx-coroutines-test са TestDispatcher уместо runBlocking. Ово даје контролу над временом, аутоматско ресетовање и изолацију тестова.
У већини сценарија runBlocking се може и треба заменити асинхроним алтернативама. За Android су то viewModelScope, lifecycleScope или CoroutineScope са правим диспечером. За тестове — TestCoroutineDispatcher и runTest.
// Лоше: runBlocking на главној нити Android-а
runBlocking(Dispatchers.Main) {
val result = networkApi.fetchData()
textView.setText(result)
}
// Добро: lifecycleScope
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { networkApi.fetchData() }
textView.setText(result)
}
Замена за тестове је runTest из kotlinx-coroutines-test. Он ствара TestCoroutineScope са виртуелним временом, омогућавајући тестирање кашњења без реалног чекања. Ово убрзава тестове и чини их детерминистичким.
Најчешћи сценарио — тестирање suspend-функција. runBlocking у тестовима омогућава синхроно чекање резултата корутине без промене архитектуре. Други сценарио — библиотеке са callback API, где се suspend-функције позивају из blocking контекста преко runBlocking-а.
// Тестирање suspend функције са runBlocking
class RepositoryTest {
@Test
fun `fetchUser returns correct data`() {
val repository = UserRepository(FakeApi())
val result = runBlocking {
repository.fetchUser("123")
}
assertEquals("John", result.name)
assertEquals("john@test.com", result.email)
}
}
За премошћавање између callback и suspend света користите CompletableDeferred у комбинацији са runBlocking уместо callback-ова — ово поједностављује ланце асинхроних операција и повећава читљивост кода.
Неправилна употреба runBlocking-а је једна од честих грешака при преласку са blocking приступа на корутине. Главни проблеми: позив на Main-нити Android-а, угњежђавање runBlocking-а, употреба унутар асинхроних функција и покретање дуготрајних операција кроз runBlocking.
Златно правило: runBlocking је мост, а не замена. Користите га само за повезивање blocking и non-blocking светова. За све остале задатке примењујте launch, async или lifecycleScope.
Често постављана питања
runBlocking је једини билдер који није suspend-функција. Он покреће event-loop у тренутној нити и не враћа контролу док се све корутине не заврше. launch и async враћају контролу одмах, извршавајући корутину у позадини.
Не препоручује се. ViewModel има уграђени viewModelScope, који аутоматски управља корутинама и отказује их при уништењу. runBlocking у ViewModel-у блокира нит и не реагује на отказивање lifecycle-а.
Користите runTest из библиотеке kotlinx-coroutines-test. Он пружа TestCoroutineScope са контролом виртуелног времена, аутоматским отказивањем и детерминистичким извршењем.
Event-loop — циклус обраде догађаја унутар runBlocking-а. Када се корутина суспендује (нпр. delay()), event-loop се пребацује на извршење других спремних корутина у истој нити. Ово ствара илузију мултизадавања без пребацивања нити.
Угњеждени runBlocking у једној нити ствара deadlock — спољашњи блок чека унутрашњи, али унутрашњи не може да почне док се спољашњи не заврши. У различитим нитима је ово прихватљиво, али изузетно се не препоручује због тежине отклањања грешака.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође