Dispatchers у Kotlin Coroutines — компоненте CoroutineContext-а које одређују niti за извршавање корутина: Main (UI nit), IO (мрежа и диск), Default (CPU-интензивни задаци) и Unconfined (тренутна nit). Сваки диспечер управља специјализованим pool-ом niti, оптимизованим за одређени тип посла. Према JetBrains водичу, 2024, избор исправног диспечера је критичан за перформансе и стабилност апликације.
Главне тачке
Dispatchers — имплементације интерфејса CoroutineDispatcher, које су елементи CoroutineContext-а. Оне одређују на којој niti или pool-у niti ће се корутина извршавати. При креирању корутине кроз launch или async, диспечер може бити прослеђен као први параметар: launch(Dispatchers.IO) { ... }. Ако диспечер није наведен, наслеђује се из спољашњег CoroutineScope-а.
Kotlin пружа четири уграђена диспечера: Main, IO, Default, Unconfined. Сваки диспечер користи сопствени pool niti, оптимизован за одређени тип операција. Исправан избор диспечера одређује перформансе апликације: грешка у избору доводи до кашњења интерфејса, неискоришћености CPU језгри или неефикасног трошења niti.
| Диспечер | Pool niti | Макс. niti | Примена |
|---|---|---|---|
| Dispatchers.Main | Једна (UI) | 1 | Ажурирање UI, LiveData, View |
| Dispatchers.IO | IO pool | 64 (limitedParallelism) | Мрежа, фајлови, базе података |
| Dispatchers.Default | CPU pool | N језгри | Сортирање, парсирање, израчунавања |
| Dispatchers.Unconfined | Тренутна nit | N/A | Посредне операције, тестови |
Dispatchers.Main — диспечер који извршава корутине на главној Android niti. Намењен је операцијама везаним за интерфејс: ажурирање TextView, позивање notifyDataSetChanged, рад са LiveData и StateFlow. У Android-у овај диспечер је имплементиран кроз Handler (Looper.getMainLooper()).
// Исправно пребацивање на Main за UI ажурирања
viewModelScope.launch(Dispatchers.IO) {
val data = repository.fetchData()
withContext(Dispatchers.Main) {
_uiState.value = data
}
}
Ако је корутина већ на Main диспечеру, додатни withContext(Dispatchers.Main) не ствара додатни overhead — диспечер проверава тренутну nit и прескаче пребацивање. withContext је пожељан начин пребацивања између диспечера.
Dispatchers.IO — диспечер оптимизован за операције улаза-излаза: HTTP захтеви (Ktor, OkHttp), читање и писање фајлова, рад са Room или SQLDelight. Користи pool од 64 niti подразумевано, скалабилан под оптерећењем. Сваки нови IO захтев може креирати додатну nit до достизања лимита.
За контролу броја истовремених IO операција користите limitedParallelism(). Ова функција креира нови диспечер са ограничењем броја паралелних niti, спречавајући исцрпљивање pool-а при масовним операцијама.
val limitedIo = Dispatchers.IO.limitedParallelism(4)
// Учитај 100 фајлова са limit-ом од 4 конкурентне операције
coroutineScope {
val files = (1..100).map { index ->
async(limitedIo) {
downloadFile("file_$index")
}
}
files.awaitAll()
}
Користите IO диспечер за све операције где корутина проводи време у чекању (I/O-bound). CPU-интензивни задаци на IO диспечеру су неефикасни — заузимају niti намењене улазу-излазу, смањујући пропусну моћ система.
Dispatchers.Default — диспечер за рачунске операције које оптерећују процесор: сортирање, филтрирање, парсирање JSON-а (Moshi, Kotlinx Serialization), обрада слика, израчунавања. Величина pool-а је једнака броју језгри процесора (али не мање од 2). Ово обезбеђује максимално оптерећење CPU-а без пребацивања контекста.
suspend fun processData(input: List<RawRecord>): List<ProcessedRecord> {
return withContext(Dispatchers.Default) {
input
.parallelStream()
.map { transform(it) }
.toList()
}
}
Немојте користити Dispatchers.Default за IO операције — то ће блокирати niti CPU pool-а које би могле обрађивати рачунске задатке. Подела на IO и Default омогућава оптимално коришћење системских ресурса: IO niti чекају улаз-излаз, CPU niti су стално заузете израчунавањима.
Dispatchers.Unconfined — специјални диспечер који не везује корутину ни за један pool. Корутина започиње извршавање у оној niti у којој је позвана launch/async, а након суспензије наставља у niti која је позвала resume. Ово понашање је погодно за посредне операције које не захтевају фиксни контекст.
fun main() = runBlocking {
launch(Dispatchers.Unconfined) {
println("Пре одлагања: ${Thread.currentThread().getName()}")
delay(500L)
println("Након одлагања: ${Thread.currentThread().getName()}")
}
}
У production коду Dispatchers.Unconfined се ретко користи. Главни случајеви: лаке трансформације пре прослеђивања података другом диспечеру и тестови. За production оптерећење користите експлицитне диспечере — Unconfined је непредвидив, јер nit извршавања зависи од имплементације resume-а.
Избор диспечера се одређује типом задатка: UI операције → Main, I/O-bound → IO, CPU-bound → Default, посредне → наслеђивање из scope-а. За Android се препоручује покретање корутине на оном диспечеру где се обавља главни посао, а пре ажурирања UI пребацити се на Main путем withContext.
За сложене сценарије комбинујте диспечере помоћу оператора +: Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler. Ово ствара CoroutineContext са одређеним диспечером, обрадом грешака и изолованом Job хијерархијом.
Често постављана питања
Dispatchers.IO користи pool до 64 niti за I/O-bound операције (чекање улаза-излаза), а Dispatchers.Default — pool према броју CPU језгри за рачунске задатке. При недостатку niti, оба pool-а могу делити niti један са другим.
Да, користите newSingleThreadContext() за једнонитни или newFixedThreadPoolContext() за фиксни pool. За production примењујте limitedParallelism() на бази постојећих диспечера — то је ефикасније од креирања нових pool-ова.
Ако Dispatchers.Main није доступан (нпр. у JUnit тесту или позадинском сервису), баца се IllegalStateException. За тестове користите TestCoroutineDispatcher, за позадинске сервисе — Dispatchers.IO или Default.
Користите Dispatchers.IO.limitedParallelism(N), где је N максимални број паралелних niti. Ово спречава исцрпљивање pool-а при масовним захтевима и даје контролисани паралелизам.
Dispatchers.Unconfined је погодан за посредне операције: лаке трансформације података пре прослеђивања другом диспечеру, тест сценарије. У Android production коду се не препоручује због неодређене niti извршавања након суспензије.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође