runBlocking — Coroutine Builder в Kotlin, който блокира текущата нишка до завършване на подадената корутина. За разлика от launch и async, той не е suspend-функция и може да бъде извикан от обикновен (blocking) код. Според документацията на JetBrains, 2024, runBlocking служи като мост между синхронния и асинхронния свят, позволявайки стартиране на корутини от main-функцията и тестове.
Основни точки
runBlocking — е функция на Kotlin, която създава нов CoroutineScope и стартира подадената корутина, блокирайки текущата нишка до нейното пълно завършване. За разлика от всички други Coroutine Builder, runBlocking не е suspend-функция и може да бъде извикан от обикновен синхронен код. Сигнатурата на runBlocking приема CoroutineContext и suspend-блок, връщайки резултат от тип T.
public fun <T> runBlocking(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> T
): T
runBlocking стартира нов event-loop в текущата нишка. Когато корутината извика suspend-функция (напр. delay() или await()), runBlocking блокира нишката и изпълнява други планирани корутини в същата нишка до възобновяване на спряната. Това е кооперативно блокиране — нишката не стои празна, а обработва други корутини.
Вътрешният механизъм на runBlocking се основава на event-loop: при извикване на suspend-функция runBlocking спира изпълнението на текущия блок и стартира други корутини от опашката. Когато suspend-функцията завърши, изпълнението се възобновява. Този цикъл продължава, докато всички корутини не завършат.
runBlocking използва собствена еднонишкова пула за изпълнение на корутини. За разлика от Dispatchers.IO или Default, runBlocking не сменя нишки — обработва всички корутини в текущата нишка, редувайки тяхното изпълнение. Това е единственият билдър, който гарантира изпълнение в същата нишка.
fun main() {
val threadName = Thread.currentThread().getName()
println("Преди runBlocking на $threadName")
val result = runBlocking {
println("Вътре в runBlocking на ${Thread.currentThread().getName()}")
delay(500L)
"Done"
}
println("След runBlocking: $result")
}
Изходът ще покаже, че и трите println се изпълняват на една нишка. runBlocking не сменя нишката, а организира кооперативна многозадачност в рамките на една нишка чрез event-loop.
runBlocking е оправдан в три сценария: точка на влизане main() в конзолни приложения, единични тестове на suspend-функции и мост — извикване на suspend-код от callback-базирани или блокиращи библиотеки. В production кода на Android използването на главната нишка е категорично забранено.
| Сценарий | Приложимост | Рискове |
|---|---|---|
| main() конзолно приложение | Да | Няма — това е точка на влизане, нишката не блокира UI |
| JUnit тестове | Да | Минимални — тестовете са по дефиниция синхронни |
| Android UI-нишка | Не | ANR, закъснения, замръзване на интерфейса |
| Callback → Coroutine | Да, с внимание | Блокиране на пула от нишки при дълги операции |
За Android тестове използвайте kotlinx-coroutines-test с TestDispatcher вместо runBlocking. Това дава контрол над времето, автоматично нулиране и изолация на тестовете.
В повечето сценарии runBlocking може и трябва да бъде заменен с асинхронни алтернативи. За Android това са viewModelScope, lifecycleScope или CoroutineScope с правилния диспечер. За тестове — TestCoroutineDispatcher и runTest.
// Лошо: runBlocking на главната нишка на Android
runBlocking(Dispatchers.Main) {
val result = networkApi.fetchData()
textView.setText(result)
}
// Добре: lifecycleScope
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { networkApi.fetchData() }
textView.setText(result)
}
Заместителят за тестове е runTest от kotlinx-coroutines-test. Той създава TestCoroutineScope с виртуално време, позволявайки тестване на закъснения без реално чакане. Това ускорява тестовете и ги прави детерминистични.
Най-честият сценарий — тестване на suspend-функции. runBlocking в тестовете позволява синхронно изчакване на резултата от корутина без промяна на архитектурата. Вторият сценарий — библиотеки с callback API, където suspend-функциите се извикват от блокиращ контекст чрез runBlocking.
// Тест на suspend функция с 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)
}
}
За мост между callback и suspend света използвайте CompletableDeferred в комбинация с runBlocking вместо callback-и — това опростява веригите от асинхронни операции и подобрява четливостта на кода.
Неправилната употреба на runBlocking е една от честите грешки при прехода от блокиращ подход към корутини. Основни проблеми: извикване на Main-нишката на Android, влагане на runBlocking, използване вътре в асинхронни функции и стартиране на дълги операции чрез runBlocking.
Златно правило: runBlocking е мост, а не заместител. Използвайте го само за свързване на блокиращия и неблокиращия свят. За всички останали задачи прилагайте launch, async или lifecycleScope.
Често задавани въпроси
runBlocking е единственият билдър, който не е suspend-функция. Той стартира event-loop в текущата нишка и не връща контрола, докато всички корутини не завършат. launch и async връщат контрола веднага, изпълнявайки корутината на заден план.
Не се препоръчва. ViewModel има вграден viewModelScope, който автоматично управлява корутините и ги отменя при унищожаване. runBlocking в ViewModel блокира нишката и не реагира на отмяна на lifecycle.
Използвайте runTest от библиотеката kotlinx-coroutines-test. Той предоставя TestCoroutineScope с контрол на виртуалното време, автоматично отменяне и детерминистично изпълнение.
Event-loop — цикълът за обработка на събития вътре в runBlocking. Когато корутина бъде спряна (напр. delay()), event-loop превключва към изпълнение на други готови корутини в същата нишка. Това създава илюзията за многозадачност без смяна на нишки.
Вложен runBlocking в една нишка създава deadlock — външният блок чака вътрешния, но вътрешният не може да започне, докато външният не завърши. В различни нишки това е приемливо, но силно не се препоръчва поради трудността при отстраняване на грешки.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също