runBlocking — är en Coroutine Builder i Kotlin som blockerar den aktuella tråden tills den angivna koroutinen har slutförts. Till skillnad från launch och async är den inte någon suspend-funktion och kan anropas från vanlig (blockerande) kod. Enligt JetBrains dokumentation, 2024, fungerar runBlocking som en brygga mellan den synkrona och asynkrona världen, vilket gör det möjligt att starta koroutiner från main-funktionen och tester.
Huvudpunkter
runBlocking — är en Kotlin-funktion som skapar ett nytt CoroutineScope och startar den angivna koroutinen, blockerar den aktuella tråden tills den är helt klar. Till skillnad från alla andra Coroutine Builder är runBlocking inte någon suspend-funktion och kan anropas från vanlig synkron kod. Signaturen för runBlocking accepterar ett CoroutineContext och ett suspend-block och returnerar ett resultat av typ T.
public fun <T> runBlocking(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> T
): T
runBlocking startar en ny händelseloop (event-loop) i den aktuella tråden. När koroutinen anropar en suspend-funktion (t.ex. delay() eller await()), blockerar runBlocking tråden och utför andra schemalagda koroutiner i samma tråd tills den suspenderade återupptas. Detta är kooperativ blockering — tråden är inte overksam utan bearbetar andra koroutiner.
Den interna mekanismen för runBlocking bygger på en händelseloop: när en suspend-funktion anropas, suspenderar runBlocking exekveringen av det aktuella blocket och startar andra koroutiner från kön. När suspend-funktionen är klar återupptas exekveringen. Denna cykel fortsätter tills alla koroutiner är klara.
runBlocking använder sin egen entrådade pool för att utföra koroutiner. Till skillnad från Dispatchers.IO eller Default växlar inte runBlocking trådar — den bearbetar alla koroutiner i den aktuella tråden och växlar mellan deras exekvering. Detta är den enda buildern som garanterar exekvering i samma tråd.
fun main() {
val threadName = Thread.currentThread().getName()
println("Före runBlocking på $threadName")
val result = runBlocking {
println("Inuti runBlocking på ${Thread.currentThread().getName()}")
delay(500L)
"Done"
}
println("Efter runBlocking: $result")
}
Utdata kommer att visa att alla tre println utförs på en tråd. runBlocking växlar inte tråd, utan organiserar kooperativ multitasking inom en tråd via händelseloopen.
runBlocking är motiverat i tre scenarier: ingångspunkten main() i konsolapplikationer, enhetstestning av suspend-funktioner och överbryggning — anrop av suspend-kod från callback-baserade eller blockerande bibliotek. I Android-produktionskod är användning på huvudtråden strängt förbjuden.
| Scenario | Tillämpbarhet | Risker |
|---|---|---|
| main() konsolapplikation | Ja | Inga — detta är ingångspunkten, tråden blockerar inte UI |
| JUnit-tester | Ja | Minimala — tester är per definition synkrona |
| Android UI-Thread | Nej | ANR, fördröjningar, frysning av gränssnittet |
| Callback → Coroutine | Ja, med försiktighet | Blockering av trådpoolen vid långvariga operationer |
För Android-tester, använd kotlinx-coroutines-test med TestDispatcher istället för runBlocking. Detta ger kontroll över tiden, automatisk återställning och testisolering.
I de flesta scenarier kan och bör runBlocking ersättas med asynkrona alternativ. För Android är dessa viewModelScope, lifecycleScope eller CoroutineScope med rätt dispatcher. För tester — TestCoroutineDispatcher och runTest.
// Dåligt: runBlocking på Android huvudtråd
runBlocking(Dispatchers.Main) {
val result = networkApi.fetchData()
textView.setText(result)
}
// Bra: lifecycleScope
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { networkApi.fetchData() }
textView.setText(result)
}
Ersättningen för tester är runTest från kotlinx-coroutines-test. Den skapar ett TestCoroutineScope med virtuell tid, vilket gör det möjligt att testa fördröjningar utan verklig väntan. Detta snabbar upp tester och gör dem deterministiska.
Det vanligaste scenariot är testning av suspend-funktioner. runBlocking i tester gör det möjligt att synkront vänta på resultatet av en koroutin utan att ändra arkitekturen. Det andra scenariot är bibliotek med callback-API, där suspend-funktioner anropas från en blockerande kontext via runBlocking.
// Testa suspend-funktion med 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)
}
}
För överbryggning mellan callback- och suspend-världen, använd CompletableDeferred i kombination med runBlocking istället för callbacks — detta förenklar kedjor av asynkrona operationer och förbättrar kodens läsbarhet.
Felaktig användning av runBlocking är ett av de vanliga misstagen vid övergången från blockerande till koroutiner. Huvudproblem: anrop på Android Main-Thread, nästling av runBlocking, användning inuti asynkrona funktioner och start av långvariga operationer via runBlocking.
Gyllene regeln: runBlocking är en brygga, inte en ersättning. Använd den endast för att koppla samman blockerande och icke-blockerande världar. För alla andra uppgifter, använd launch, async eller lifecycleScope.
Vanliga frågor
runBlocking är den enda buildern som inte är en suspend-funktion. Den startar en händelseloop i den aktuella tråden och lämnar inte tillbaka kontrollen förrän alla koroutiner har slutförts. launch och async lämnar omedelbart tillbaka kontrollen och utför koroutinen i bakgrunden.
Rekommenderas inte. ViewModel har inbyggd viewModelScope som automatiskt hanterar koroutiner och avbryter dem vid förstöring. runBlocking i ViewModel blockerar tråden och reagerar inte på livscykelavbrott.
Använd runTest från biblioteket kotlinx-coroutines-test. Den ger en TestCoroutineScope med virtuell tidskontroll, automatisk avbrytning och deterministisk exekvering.
Event-loop — händelsebearbetningscykeln inuti runBlocking. När en koroutin suspenderas (t.ex. delay()), växlar händelseloopen till att utföra andra redo koroutiner i samma tråd. Detta skapar illusionen av multitasking utan trådbyte.
Nästlad runBlocking i en enda tråd skapar en deadlock — det yttre blocket väntar på det inre, men det inre kan inte börja förrän det yttre har slutförts. I olika trådar är detta acceptabelt men starkt avrått på grund av svårigheten med felsökning.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också