runBlocking — ein Coroutine Builder in Kotlin, der den aktuellen Thread blockiert, bis die übergebene Coroutine abgeschlossen ist. Anders als launch und async ist es keine suspend-Funktion und kann aus gewöhnlichem (blockierendem) Code aufgerufen werden. Laut der JetBrains-Dokumentation, 2024, dient runBlocking als Brücke zwischen der synchronen und asynchronen Welt und ermöglicht das Starten von Coroutinen aus der main-Funktion und Tests.
Wichtigste Erkenntnisse
runBlocking ist eine Kotlin-Funktion, die einen neuen CoroutineScope erstellt und die übergebene Coroutine startet, wobei der aktuelle Thread blockiert wird, bis sie vollständig abgeschlossen ist. Anders als alle anderen Coroutine Builder ist runBlocking keine suspend-Funktion und kann aus gewöhnlichem synchronem Code aufgerufen werden. Die Signatur von runBlocking nimmt einen CoroutineContext und einen suspend-Block entgegen und gibt ein Ergebnis vom Typ T zurück.
public fun <T> runBlocking(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> T
): T
runBlocking startet eine neue Event-Loop auf dem aktuellen Thread. Wenn die Coroutine eine suspend-Funktion (z. B. delay() oder await()) aufruft, blockiert runBlocking den Thread und führt andere geplante Coroutinen auf demselben Thread aus, bis die angehaltene fortgesetzt wird. Dies ist kooperatives Blockieren — der Thread ist nicht untätig, sondern verarbeitet andere Coroutinen.
Der interne Mechanismus von runBlocking basiert auf einer Event-Loop: Beim Aufruf einer suspend-Funktion pausiert runBlocking die Ausführung des aktuellen Blocks und führt andere Coroutinen aus der Warteschlange aus. Wenn die suspend-Funktion abgeschlossen ist, wird die Ausführung fortgesetzt. Dieser Zyklus wiederholt sich, bis alle Coroutinen abgeschlossen sind.
runBlocking verwendet einen eigenen Single-Thread-Pool zur Ausführung von Coroutinen. Anders als Dispatchers.IO oder Default wechselt runBlocking keine Threads — es verarbeitet alle Coroutinen auf dem aktuellen Thread und verschränkt ihre Ausführung. Dies ist der einzige Builder, der die Ausführung auf demselben Thread garantiert.
fun main() {
val threadName = Thread.currentThread().getName()
println("Vor runBlocking auf $threadName")
val result = runBlocking {
println("Innerhalb von runBlocking auf ${Thread.currentThread().getName()}")
delay(500L)
"Done"
}
println("Nach runBlocking: $result")
}
Die Ausgabe zeigt, dass alle drei println-Anweisungen auf demselben Thread ausgeführt werden. runBlocking wechselt keine Threads, sondern organisiert kooperatives Multitasking innerhalb eines einzelnen Threads mithilfe einer Event-Loop.
runBlocking ist in drei Szenarien gerechtfertigt: dem main()-Einstiegspunkt in Konsolenanwendungen, Komponententests für suspend-Funktionen und als Brücke — dem Aufruf von suspend-Code aus callback-basierten oder blockierenden Bibliotheken. In Produktions-Android-Code ist die Verwendung auf dem Hauptthread strengstens verboten.
| Szenario | Anwendbarkeit | Risiken |
|---|---|---|
| main() einer Konsolenanwendung | Ja | Keine — es ist der Einstiegspunkt, der Thread blockiert keine UI |
| JUnit-Tests | Ja | Minimal — Tests sind per Definition synchron |
| Android-UI-Thread | Nein | ANR, Verzögerungen, Einfrieren der Oberfläche |
| Callback → Coroutine | Ja, mit Vorsicht | Thread-Pool-Blockierung bei langen Operationen |
Für Android-Tests verwenden Sie kotlinx-coroutines-test mit TestDispatcher anstelle von runBlocking. Dies bietet Zeitkontrolle, automatische Bereinigung und Testisolierung.
In den meisten Szenarien kann und sollte runBlocking durch asynchrone Alternativen ersetzt werden. Für Android sind dies viewModelScope, lifecycleScope oder CoroutineScope mit dem richtigen Dispatcher. Für Tests — TestCoroutineDispatcher und runTest.
// Schlecht: runBlocking auf dem Android-Hauptthread
runBlocking(Dispatchers.Main) {
val result = networkApi.fetchData()
textView.setText(result)
}
// Gut: lifecycleScope
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { networkApi.fetchData() }
textView.setText(result)
}
Für Tests ist der Ersatz runTest aus kotlinx-coroutines-test. Es erstellt einen TestCoroutineScope mit virtueller Zeit, sodass Verzögerungen ohne echtes Warten getestet werden können. Dies beschleunigt Tests und macht sie deterministisch.
Das häufigste Szenario ist das Testen von suspend-Funktionen. runBlocking in Tests ermöglicht es, synchron auf ein Coroutinen-Ergebnis zu warten, ohne die Architektur zu ändern. Das zweite Szenario sind Bibliotheken mit Callback-APIs, bei denen suspend-Funktionen aus einem blockierenden Kontext über runBlocking aufgerufen werden.
// Suspend-Funktion mit runBlocking testen
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)
}
}
Für die Brücke zwischen Callback- und suspend-Welten verwenden Sie CompletableDeferred in Kombination mit runBlocking anstelle von Callbacks — dies vereinfacht asynchrone Operationsketten und verbessert die Lesbarkeit des Codes.
Die unsachgemäße Verwendung von runBlocking ist einer der häufigsten Fehler beim Übergang von einem blockierenden Ansatz zu Coroutinen. Hauptprobleme: Aufruf auf dem Android-Hauptthread, Verschachtelung von runBlocking, Verwendung innerhalb asynchroner Funktionen und Start langlaufender Operationen über runBlocking.
Goldene Regel: runBlocking ist eine Brücke, kein Ersatz. Verwenden Sie es nur, um blockierende und nicht-blockierende Welten zu verbinden. Für alle anderen Aufgaben verwenden Sie launch, async oder lifecycleScope.
Häufig gestellte Fragen
runBlocking ist der einzige Builder, der keine suspend-Funktion ist. Es startet eine Event-Loop auf dem aktuellen Thread und gibt die Kontrolle erst zurück, wenn alle Coroutinen abgeschlossen sind. launch und async geben sofort die Kontrolle zurück und führen die Coroutine im Hintergrund aus.
Nicht empfohlen. ViewModel verfügt über einen integrierten viewModelScope, der Coroutinen automatisch verwaltet und bei Zerstörung abbricht. runBlocking im ViewModel blockiert den Thread und reagiert nicht auf den Lifecycle-Abbruch.
Verwenden Sie runTest aus der Bibliothek kotlinx-coroutines-test. Es bietet einen TestCoroutineScope mit virtueller Zeitsteuerung, automatischem Abbruch und deterministischer Ausführung.
Event-Loop ist ein Ereignisverarbeitungszyklus innerhalb von runBlocking. Wenn eine Coroutine angehalten wird (z. B. delay()), wechselt die Event-Loop zur Ausführung anderer bereiter Coroutinen auf demselben Thread. Dies erzeugt die Illusion von Multitasking ohne Thread-Wechsel.
Verschachteltes runBlocking auf demselben Thread erzeugt einen Deadlock — der äußere Block wartet auf den inneren, aber der innere kann nicht beginnen, bis der äußere beendet ist. Auf verschiedenen Threads ist es erlaubt, aber aufgrund der Debugging-Komplexität stark abgeraten.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch