runBlocking — Coroutine Builder w Kotlinie, który blokuje bieżący wątek do czasu zakończenia przekazanej korutyny. W przeciwieństwie do launch i async nie jest funkcją suspend i może być wywoływany ze zwykłego (blokującego) kodu. Według dokumentacji JetBrains, 2024, runBlocking służy jako most między synchronicznym a asynchronicznym światem, umożliwiając uruchamianie korutyn z funkcji main i testów.
Najważniejsze
runBlocking — to funkcja Kotlina, która tworzy nowy CoroutineScope i uruchamia przekazaną korutynę, blokując bieżący wątek do jej całkowitego zakończenia. W przeciwieństwie do wszystkich innych Coroutine Builder, runBlocking nie jest funkcją suspend i może być wywoływany ze zwykłego synchronicznego kodu. Sygnatura runBlocking przyjmuje CoroutineContext i blok suspend, zwracając wynik typu T.
public fun <T> runBlocking(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> T
): T
runBlocking uruchamia nową pętlę zdarzeń (event-loop) w bieżącym wątku. Gdy korutyna wywołuje funkcję suspend (np. delay() lub await()), runBlocking blokuje wątek i wykonuje inne zaplanowane korutyny w tym samym wątku do czasu wznowienia zawieszonej. Jest to kooperatywne blokowanie — wątek nie marnuje czasu, ale przetwarza inne korutyny.
Wewnętrzny mechanizm runBlocking opiera się na pętli zdarzeń: przy wywołaniu funkcji suspend runBlocking wstrzymuje wykonanie bieżącego bloku i uruchamia inne korutyny z kolejki. Gdy funkcja suspend się zakończy, wykonanie zostaje wznowione. Cykl ten trwa, aż wszystkie korutyny się zakończą.
runBlocking używa własnej jednowątkowej puli do wykonywania korutyn. W przeciwieństwie do Dispatchers.IO lub Default, runBlocking nie przełącza wątków — przetwarza wszystkie korutyny w bieżącym wątku, przeplatając ich wykonanie. Jest to jedyny builder gwarantujący wykonanie w tym samym wątku.
fun main() {
val threadName = Thread.currentThread().getName()
println("Przed runBlocking na $threadName")
val result = runBlocking {
println("Wewnątrz runBlocking na ${Thread.currentThread().getName()}")
delay(500L)
"Done"
}
println("Po runBlocking: $result")
}
Wynik pokaże, że wszystkie trzy println wykonują się w jednym wątku. runBlocking nie przełącza wątku, ale organizuje kooperatywną wielozadaniowość wewnątrz jednego wątku za pomocą pętli zdarzeń.
runBlocking jest uzasadniony w trzech scenariuszach: punkt wejścia main() w aplikacjach konsolowych, testy jednostkowe funkcji suspend oraz pomost — wywoływanie kodu suspend z bibliotek opartych na callback lub blokujących. W kodzie produkcyjnym Androida użycie na głównym wątku jest kategorycznie zabronione.
| Scenariusz | Stosowalność | Ryzyka |
|---|---|---|
| main() aplikacji konsolowej | Tak | Brak — to punkt wejścia, wątek nie blokuje UI |
| Testy JUnit | Tak | Minimalne — testy są z definicji synchroniczne |
| Android UI-Thread | Nie | ANR, opóźnienia, zawieszenie interfejsu |
| Callback → Coroutine | Tak, ostrożnie | Blokowanie puli wątków przy długotrwałych operacjach |
Do testów na Androidzie używaj kotlinx-coroutines-test z TestDispatcher zamiast runBlocking. Daje to kontrolę nad czasem, automatyczne resetowanie i izolację testów.
Większość scenariuszy runBlocking można i należy zastąpić alternatywami asynchronicznymi. Dla Androida są to viewModelScope, lifecycleScope lub CoroutineScope z odpowiednim dyspozytorem. Dla testów — TestCoroutineDispatcher i runTest.
// Źle: runBlocking na głównym wątku Androida
runBlocking(Dispatchers.Main) {
val result = networkApi.fetchData()
textView.setText(result)
}
// Dobrze: lifecycleScope
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { networkApi.fetchData() }
textView.setText(result)
}
Zamiennikiem dla testów jest runTest z kotlinx-coroutines-test. Tworzy on TestCoroutineScope z wirtualnym czasem, pozwalając testować opóźnienia bez rzeczywistego czekania. To przyspiesza testy i czyni je deterministycznymi.
Najczęstszy scenariusz to testowanie funkcji suspend. runBlocking w testach pozwala synchronicznie poczekać na wynik korutyny bez zmiany architektury. Drugi scenariusz to biblioteki z callback API, gdzie funkcje suspend są wywoływane z kontekstu blokującego przez runBlocking.
// Test funkcji suspend za pomocą 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)
}
}
Do pomostowania między światem callback a suspend używaj CompletableDeferred w kombinacji z runBlocking zamiast callbacków — upraszcza to łańcuchy operacji asynchronicznych i zwiększa czytelność kodu.
Nieprawidłowe użycie runBlocking to jeden z częstych błędów przy przejściu z podejścia blokującego na korutyny. Główne problemy: wywołanie na Main-Thread Androida, zagnieżdżanie runBlocking, użycie wewnątrz funkcji asynchronicznych i uruchamianie długotrwałych operacji przez runBlocking.
Złota zasada: runBlocking to most, a nie zamiennik. Używaj go tylko do łączenia świata blokującego i nieblokującego. Do wszystkich innych zadań stosuj launch, async lub lifecycleScope.
Często zadawane pytania
runBlocking to jedyny builder, który nie jest funkcją suspend. Uruchamia pętlę zdarzeń w bieżącym wątku i nie zwraca kontroli, dopóki wszystkie korutyny nie zostaną zakończone. launch i async zwracają kontrolę natychmiast, wykonując korutynę w tle.
Nie zaleca się. ViewModel ma wbudowany viewModelScope, który automatycznie zarządza korutynami i anuluje je przy zniszczeniu. runBlocking w ViewModel blokuje wątek i nie reaguje na anulowanie lifecycle.
Używaj runTest z biblioteki kotlinx-coroutines-test. Zapewnia on TestCoroutineScope z kontrolą wirtualnego czasu, automatycznym anulowaniem i deterministycznym wykonaniem.
Event-loop — pętla przetwarzania zdarzeń wewnątrz runBlocking. Gdy korutyna jest zawieszona (np. delay()), event-loop przełącza się na wykonanie innych gotowych korutyn w tym samym wątku. Tworzy to iluzję wielozadaniowości bez przełączania wątków.
Zagnieżdżony runBlocking w jednym wątku tworzy deadlock — zewnętrzny blok czeka na wewnętrzny, ale wewnętrzny nie może się rozpocząć, dopóki zewnętrzny się nie zakończy. W różnych wątkach jest to dopuszczalne, ale skrajnie niezalecane ze względu na trudność debugowania.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również