runBlocking — Coroutine Builder Kotlinban, amely blokkolja az aktuális szálat az átadott korutin befejezéséig. A launch és async függvényektől eltérően nem suspend-függvény, és hagyományos (blokkoló) kódból is meghívható. A JetBrains dokumentációja szerint, 2024, a runBlocking hídként szolgál a szinkron és aszinkron világ között, lehetővé téve korutinok indítását a main-függvényből és tesztekből.
Főbb pontok
runBlocking — egy Kotlin-függvény, amely új CoroutineScope-ot hoz létre és elindítja az átadott korutint, blokkolva az aktuális szálat annak teljes befejezéséig. Az összes többi Coroutine Builder-től eltérően a runBlocking nem suspend-függvény, és hagyományos szinkron kódból is meghívható. A runBlocking aláírása egy CoroutineContext-et és egy suspend-blokkot fogad el, és T típusú eredményt ad vissza.
public fun <T> runBlocking(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> T
): T
A runBlocking egy új eseményhurkot (event-loop) indít az aktuális szálban. Amikor a korutin meghív egy suspend-függvényt (pl. delay() vagy await()), a runBlocking blokkolja a szálat és más ütemezett korutinokat hajt végre ugyanabban a szálban a felfüggesztett korutin folytatásáig. Ez kooperatív blokkolás — a szál nem tétlenkedik, hanem más korutinokat dolgoz fel.
A runBlocking belső mechanizmusa eseményhurokon alapul: suspend-függvény meghívásakor a runBlocking felfüggeszti az aktuális blokk végrehajtását és más korutinokat indít a sorból. Amikor a suspend-függvény befejeződik, a végrehajtás folytatódik. Ez a ciklus addig tart, amíg az összes korutin be nem fejeződik.
A runBlocking a saját egyszálú poolját használja a korutinok végrehajtására. A Dispatchers.IO-tól vagy Default-tól eltérően a runBlocking nem vált szálat — az összes korutint az aktuális szálban dolgozza fel, váltogatva a végrehajtásukat. Ez az egyetlen builder, amely garantálja a végrehajtást ugyanabban a szálban.
fun main() {
val threadName = Thread.currentThread().getName()
println("runBlocking előtt ezen a szálon: $threadName")
val result = runBlocking {
println("runBlocking-en belül ezen a szálon: ${Thread.currentThread().getName()}")
delay(500L)
"Done"
}
println("runBlocking után: $result")
}
A kimenet megmutatja, hogy mindhárom println ugyanazon a szálon fut. A runBlocking nem vált szálat, hanem eseményhurok segítségével kooperatív többfeladatos munkát szervez egy szálon belül.
A runBlocking három esetben indokolt: a main() belépési pont konzolos alkalmazásokban, a suspend-függvények egységtesztelése és híd — suspend-kód meghívása callback-alapú vagy blokkoló könyvtárakból. Android production kódban a fő szálon történő használat szigorúan tilos.
| Forgatókönyv | Alkalmazhatóság | Kockázatok |
|---|---|---|
| main() konzolalkalmazás | Igen | Nincs — ez a belépési pont, a szál nem blokkol UI-t |
| JUnit tesztek | Igen | Minimális — a tesztek definíció szerint szinkronok |
| Android UI-Thread | Nem | ANR, késések, a felület lefagyása |
| Callback → Coroutine | Igen, óvatosan | Szálpool blokkolása hosszú műveleteknél |
Android tesztekhez használj kotlinx-coroutines-test-et TestDispatcher-rel a runBlocking helyett. Ez időbeli vezérlést, automatikus visszaállítást és tesztizolációt biztosít.
A legtöbb esetben a runBlocking helyettesíthető és helyettesítendő aszinkron alternatívákkal. Android esetében ezek a viewModelScope, a lifecycleScope vagy a CoroutineScope megfelelő diszpécserrel. Tesztekhez — TestCoroutineDispatcher és runTest.
// Rossz: runBlocking az Android fő szálán
runBlocking(Dispatchers.Main) {
val result = networkApi.fetchData()
textView.setText(result)
}
// Jó: lifecycleScope
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { networkApi.fetchData() }
textView.setText(result)
}
A tesztek helyettesítője a runTest a kotlinx-coroutines-test-ből. Virtuális idővel rendelkező TestCoroutineScope-ot hoz létre, lehetővé téve a késleltetések tesztelését valódi várakozás nélkül. Ez felgyorsítja a teszteket és determinisztikussá teszi azokat.
A leggyakoribb eset — suspend-függvények tesztelése. A runBlocking a tesztekben lehetővé teszi a korutin eredményének szinkron megvárását anélkül, hogy megváltoztatná az architektúrát. A második eset — callback API-val rendelkező könyvtárak, ahol a suspend-függvények blokkoló kontextusból kerülnek meghívásra a runBlocking segítségével.
// Suspend függvény tesztelése runBlocking-gal
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)
}
}
A callback és a suspend világ közötti hídként használj CompletableDeferred-et runBlocking kombinációban a callback-ek helyett — ez leegyszerűsíti az aszinkron műveletek láncait és javítja a kód olvashatóságát.
A runBlocking helytelen használata gyakori hiba a blokkoló megközelítésről korutinokra való áttéréskor. Főbb problémák: meghívás az Android Main-Thread-en, runBlocking egymásba ágyazása, használat aszinkron függvényeken belül és hosszú műveletek indítása runBlocking segítségével.
Aranyszabály: a runBlocking egy híd, nem helyettesítés. Csak a blokkoló és nem blokkoló világok összekötésére használjuk. Minden más feladathoz alkalmazzuk a launch, async vagy lifecycleScope függvényeket.
Gyakran Ismételt Kérdések
runBlocking az egyetlen builder, amely nem suspend-függvény. Eseményhurkot indít az aktuális szálban, és nem adja vissza a vezérlést, amíg az összes korutin be nem fejeződött. A launch és async azonnal visszaadják a vezérlést, a korutint a háttérben futtatva.
Nem ajánlott. A ViewModel beépített viewModelScope-pal rendelkezik, amely automatikusan kezeli a korutinokat és megszakítja azokat megsemmisüléskor. A runBlocking a ViewModel-ben blokkolja a szálat és nem reagál a lifecycle megszakítására.
Használd a runTest-et a kotlinx-coroutines-test könyvtárból. Virtuális idővezérléssel, automatikus megszakítással és determinisztikus végrehajtással rendelkező TestCoroutineScope-ot biztosít.
Event-loop — az eseményfeldolgozási ciklus a runBlocking-en belül. Amikor egy korutin felfüggesztésre kerül (pl. delay()), az event-loop átvált más kész korutinok végrehajtására ugyanabban a szálban. Ez a többfeladatos munka illúzióját kelti szálváltás nélkül.
Beágyazott runBlocking egy szálban deadlock-ot hoz létre — a külső blokk vár a belsőre, de a belső nem kezdődhet el, amíg a külső be nem fejeződik. Különböző szálakban ez elfogadható, de a hibakeresés nehézsége miatt erősen nem ajánlott.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is