runBlocking — Coroutine Builder v Kotlinu, který blokuje aktuální vlákno do dokončení předané korutiny. Na rozdíl od launch a async není funkcí suspend a může být volán z běžného (blokujícího) kódu. Podle dokumentace JetBrains, 2024, slouží runBlocking jako most mezi synchronním a asynchronním světem a umožňuje spouštět korutiny z funkce main a testů.
Hlavní body
runBlocking — je funkce Kotlinu, která vytváří nový CoroutineScope a spouští předanou korutinu, blokuje aktuální vlákno do jejího úplného dokončení. Na rozdíl od všech ostatních Coroutine Builder není runBlocking funkcí suspend a může být volán z běžného synchronního kódu. Signatura runBlocking přijímá CoroutineContext a suspend-blok a vrací výsledek typu T.
public fun <T> runBlocking(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> T
): T
runBlocking spouští novou smyčku událostí (event-loop) v aktuálním vlákně. Když korutina zavolá funkci suspend (např. delay() nebo await()), runBlocking zablokuje vlákno a provádí další naplánované korutiny ve stejném vlákně, dokud není pozastavená obnovena. Toto je kooperativní blokování — vlákno není nečinné, ale zpracovává jiné korutiny.
Vnitřní mechanismus runBlocking je založen na event-loop: při volání funkce suspend runBlocking pozastaví provádění aktuálního bloku a spouští další korutiny z fronty. Po dokončení funkce suspend je provádění obnoveno. Tento cyklus pokračuje, dokud nejsou dokončeny všechny korutiny.
runBlocking používá vlastní jednovláknový fond pro provádění korutin. Na rozdíl od Dispatchers.IO nebo Default runBlocking nemění vlákna — zpracovává všechny korutiny v aktuálním vlákně, střídá jejich provádění. Toto je jediný builder, který zaručuje provádění ve stejném vlákně.
fun main() {
val threadName = Thread.currentThread().getName()
println("Před runBlocking na $threadName")
val result = runBlocking {
println("Uvnitř runBlocking na ${Thread.currentThread().getName()}")
delay(500L)
"Done"
}
println("Po runBlocking: $result")
}
Výstup ukáže, že všechny tři println se provádějí na jednom vlákně. runBlocking nemění vlákno, ale organizuje kooperativní multitasking v rámci jednoho vlákna pomocí event-loop.
runBlocking je opodstatněný ve třech scénářích: vstupní bod main() v konzolových aplikacích, jednotkové testy funkcí suspend a přemostění — volání kódu suspend z callbackových nebo blokujících knihoven. V produkčním kódu Android je použití na hlavním vlákně kategoricky zakázáno.
| Scénář | Použitelnost | Rizika |
|---|---|---|
| main() konzolové aplikace | Ano | Žádné — to je vstupní bod, vlákno neblokuje UI |
| JUnit testy | Ano | Minimální — testy jsou z definice synchronní |
| Android UI-Thread | Ne | ANR, zpoždění, zamrznutí rozhraní |
| Callback → Coroutine | Ano, opatrně | Blokování fondu vláken při dlouhých operacích |
Pro Android testy používejte kotlinx-coroutines-test s TestDispatcher místo runBlocking. To poskytuje kontrolu nad časem, automatický reset a izolaci testů.
Ve většině scénářů lze a je třeba runBlocking nahradit asynchronními alternativami. Pro Android jsou to viewModelScope, lifecycleScope nebo CoroutineScope se správným dispečerem. Pro testy — TestCoroutineDispatcher a runTest.
// Špatně: runBlocking na hlavním vlákně Android
runBlocking(Dispatchers.Main) {
val result = networkApi.fetchData()
textView.setText(result)
}
// Dobře: lifecycleScope
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { networkApi.fetchData() }
textView.setText(result)
}
Náhradou pro testy je runTest z kotlinx-coroutines-test. Vytváří TestCoroutineScope s virtuálním časem, což umožňuje testovat zpoždění bez reálného čekání. To testy zrychluje a činí je deterministickými.
Nejčastějším scénářem je testování funkcí suspend. runBlocking v testech umožňuje synchronně počkat na výsledek korutiny bez změny architektury. Druhým scénářem jsou knihovny s callback API, kde jsou funkce suspend volány z blokujícího kontextu přes runBlocking.
// Test suspend funkce s 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)
}
}
Pro přemostění mezi callback a suspend světem používejte CompletableDeferred v kombinaci s runBlocking místo callbacků — to zjednodušuje řetězce asynchronních operací a zvyšuje čitelnost kódu.
Nesprávné použití runBlocking je jednou z častých chyb při přechodu z blokujícího přístupu na korutiny. Hlavní problémy: volání na Main-Thread Android, vnořování runBlocking, použití uvnitř asynchronních funkcí a spouštění dlouhých operací přes runBlocking.
Zlaté pravidlo: runBlocking je most, nikoli náhrada. Používejte ho pouze pro propojení blokujícího a neblokujícího světa. Pro všechny ostatní úkoly používejte launch, async nebo lifecycleScope.
Často kladené otázky
runBlocking je jediný builder, který není funkcí suspend. Spouští event-loop v aktuálním vlákně a nevrací řízení, dokud nejsou dokončeny všechny korutiny. launch a async vracejí řízení okamžitě a provádějí korutinu na pozadí.
Nedoporučuje se. ViewModel má vestavěný viewModelScope, který automaticky spravuje korutiny a ruší je při zničení. runBlocking ve ViewModel blokuje vlákno a nereaguje na zrušení lifecycle.
Použijte runTest z knihovny kotlinx-coroutines-test. Poskytuje TestCoroutineScope s řízením virtuálního času, automatickým zrušením a deterministickým prováděním.
Event-loop — smyčka zpracování událostí uvnitř runBlocking. Když je korutina pozastavena (např. delay()), event-loop přepne na provádění jiných připravených korutin ve stejném vlákně. To vytváří iluzi multitaskingu bez přepínání vláken.
Vnořený runBlocking v jednom vlákně vytváří deadlock — vnější blok čeká na vnitřní, ale vnitřní nemůže začít, dokud vnější neskončí. V různých vláknech je to přijatelné, ale důrazně se nedoporučuje kvůli obtížnosti ladění.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také