runBlocking — este un Coroutine Builder în Kotlin care blochează firul de execuție curent până la finalizarea corutinei transmise. Spre deosebire de launch și async, nu este o funcție suspend și poate fi apelat din cod obișnuit (blocant). Conform documentației JetBrains, 2024, runBlocking servește ca punte între lumea sincronă și asincronă, permițând lansarea corutinelor din funcția main și teste.
Principalele puncte
runBlocking — este o funcție Kotlin care creează un nou CoroutineScope și lansează corutina transmisă, blocând firul curent până la finalizarea sa completă. Spre deosebire de toți ceilalți Coroutine Builder, runBlocking nu este o funcție suspend și poate fi apelat din cod sincron obișnuit. Semnătura runBlocking acceptă un CoroutineContext și un bloc suspend, returnând un rezultat de tip T.
public fun <T> runBlocking(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> T
): T
runBlocking lansează o nouă buclă de evenimente (event-loop) în firul curent. Când corutina apelează o funcție suspend (de exemplu, delay() sau await()), runBlocking blochează firul și execută alte corutini programate în același fir până la reluarea celei suspendate. Aceasta este blocarea cooperativă — firul nu staționează, ci procesează alte corutini.
Mecanismul intern al runBlocking se bazează pe event-loop: la apelarea unei funcții suspend, runBlocking suspendă executarea blocului curent și lansează alte corutini din coadă. Când funcția suspend se finalizează, executarea este reluată. Acest ciclu continuă până când toate corutinele se finalizează.
runBlocking utilizează propriul său pool monofir pentru executarea corutinelor. Spre deosebire de Dispatchers.IO sau Default, runBlocking nu comută firele — procesează toate corutinele în firul curent, intercalând execuția lor. Acesta este singurul builder care garantează executarea în același fir.
fun main() {
val threadName = Thread.currentThread().getName()
println("Înainte de runBlocking pe $threadName")
val result = runBlocking {
println("În interiorul runBlocking pe ${Thread.currentThread().getName()}")
delay(500L)
"Done"
}
println("După runBlocking: $result")
}
Rezultatul va arăta că toate cele trei println se execută pe un singur fir. runBlocking nu comută firul, ci organizează multitasking cooperativ într-un singur fir prin intermediul event-loop.
runBlocking este justificat în trei scenarii: punctul de intrare main() în aplicațiile consolă, testele unitare ale funcțiilor suspend și puntea — apelarea codului suspend din biblioteci bazate pe callback sau blocante. În codul de producție Android, utilizarea pe firul principal este categoric interzisă.
| Scenariu | Aplicabilitate | Riscuri |
|---|---|---|
| main() aplicație consolă | Da | Nu — este punctul de intrare, firul nu blochează UI |
| Teste JUnit | Da | Minime — testele sunt prin definiție sincrone |
| Android UI-Thread | Nu | ANR, întârzieri, înghețarea interfeței |
| Callback → Coroutine | Da, cu prudență | Blocarea pool-ului de fire la operații de lungă durată |
Pentru testele Android folosește kotlinx-coroutines-test cu TestDispatcher în loc de runBlocking. Acest lucru oferă control asupra timpului, resetare automată și izolare a testelor.
În majoritatea scenariilor, runBlocking poate și trebuie înlocuit cu alternative asincrone. Pentru Android, acestea sunt viewModelScope, lifecycleScope sau CoroutineScope cu un dispatcher corespunzător. Pentru teste — TestCoroutineDispatcher și runTest.
// Rău: runBlocking pe firul principal Android
runBlocking(Dispatchers.Main) {
val result = networkApi.fetchData()
textView.setText(result)
}
// Bine: lifecycleScope
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { networkApi.fetchData() }
textView.setText(result)
}
Înlocuitorul pentru teste este runTest din kotlinx-coroutines-test. Acesta creează un TestCoroutineScope cu timp virtual, permițând testarea întârzierilor fără așteptare reală. Acest lucru accelerează testele și le face deterministe.
Cel mai frecvent scenariu — testarea funcțiilor suspend. runBlocking în teste permite așteptarea sincronă a rezultatului corutinei fără modificarea arhitecturii. Al doilea scenariu — biblioteci cu callback API, unde funcțiile suspend sunt apelate din context blocant prin runBlocking.
// Test funcție suspend cu 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)
}
}
Pentru puntea între lumea callback și suspend, folosește CompletableDeferred în combinație cu runBlocking în loc de callback-uri — acest lucru simplifică lanțurile de operații asincrone și îmbunătățește lizibilitatea codului.
Utilizarea incorectă a runBlocking este una dintre greșelile frecvente la trecerea de la abordarea blocantă la corutini. Principalele probleme: apelarea pe Main-Thread Android, înnestarea runBlocking, utilizarea în interiorul funcțiilor asincrone și lansarea operațiilor de lungă durată prin runBlocking.
Regula de aur: runBlocking este o punte, nu o înlocuire. Folosește-l doar pentru conectarea lumilor blocantă și neblocantă. Pentru toate celelalte sarcini, aplică launch, async sau lifecycleScope.
Întrebări frecvente
runBlocking este singurul builder care nu este o funcție suspend. Acesta lansează o event-loop în firul curent și nu returnează controlul până când toate corutinele nu sunt finalizate. launch și async returnează controlul imediat, executând corutina în fundal.
Nu se recomandă. ViewModel are viewModelScope încorporat, care gestionează automat corutinele și le anulează la distrugere. runBlocking în ViewModel blochează firul și nu reacționează la anularea lifecycle.
Folosește runTest din biblioteca kotlinx-coroutines-test. Acesta oferă un TestCoroutineScope cu control al timpului virtual, anulare automată și executare deterministă.
Event-loop — bucla de procesare a evenimentelor în interiorul runBlocking. Când o corutină este suspendată (de exemplu, delay()), event-loop comută la executarea altor corutini gata în același fir. Aceasta creează iluzia multitasking-ului fără comutarea firelor.
runBlocking înnestat într-un singur fir creează un deadlock — blocul extern așteaptă cel intern, dar cel intern nu poate începe până când cel extern nu se finalizează. În fire diferite, acest lucru este acceptabil, dar extrem nerecomandat din cauza complexității depanării.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și