Dispatchers a Kotlin Coroutines-ben — a CoroutineContext komponensei, amelyek meghatározzák a korutinok végrehajtásához használt szálakat: Main (UI-szál), IO (hálózat és lemez), Default (CPU-intenzív feladatok) és Unconfined (aktuális szál). Minden diszpécser egy specializált szálkészletet kezel, amely az adott munkatípushoz van optimalizálva. A JetBrains, 2024 útmutatója szerint a megfelelő diszpécser kiválasztása kritikus fontosságú az alkalmazás teljesítménye és stabilitása szempontjából.
Főbb pontok
Dispatchers — a CoroutineDispatcher interfész implementációi, amelyek a CoroutineContext elemei. Meghatározzák, hogy melyik szálon vagy szálkészleten hajtódik végre a korutin. Korutin launch vagy async segítségével történő létrehozásakor a diszpécser első paraméterként adható át: launch(Dispatchers.IO) { ... }. Ha nincs diszpécser megadva, a külső CoroutineScope-ból öröklődik.
A Kotlin négy beépített diszpécsert kínál: Main, IO, Default, Unconfined. Minden diszpécser saját, az adott művelettípushoz optimalizált szálkészletet használ. A diszpécser helyes megválasztása határozza meg az alkalmazás teljesítményét: a rossz választás a felület lassulásához, a CPU-magok kihasználatlanságához vagy a szálak nem hatékony felhasználásához vezet.
| Diszpécser | Szálkészlet | Max. szálak | Használat |
|---|---|---|---|
| Dispatchers.Main | Egy (UI) | 1 | UI frissítés, LiveData, View |
| Dispatchers.IO | IO készlet | 64 (limitedParallelism) | Hálózat, fájlok, adatbázisok |
| Dispatchers.Default | CPU készlet | N mag | Rendezés, elemzés, számítások |
| Dispatchers.Unconfined | Aktuális szál | N/A | Közbenső műveletek, tesztek |
Dispatchers.Main — a korutinokat az Android főszálán végrehajtó diszpécser. A felülettel kapcsolatos műveletekre szolgál: TextView frissítése, notifyDataSetChanged meghívása, LiveData és StateFlow használata. Androidban ez a diszpécser Handleren (Looper.getMainLooper()) keresztül van implementálva.
// Helyes átváltás Main-re UI frissítésekhez
viewModelScope.launch(Dispatchers.IO) {
val data = repository.fetchData()
withContext(Dispatchers.Main) {
_uiState.value = data
}
}
Ha a korutin már a Main-diszpécseren van, a további withContext(Dispatchers.Main) nem okoz többletterhelést — a diszpécser ellenőrzi az aktuális szálat és kihagyja a váltást. withContext az előnyben részesített mód a diszpécserek közötti váltásra.
Dispatchers.IO — bemeneti-kimeneti műveletekre optimalizált diszpécser: HTTP-kérések (Ktor, OkHttp), fájlok olvasása és írása, Room vagy SQLDelight használata. Alapértelmezés szerint 64 szálból álló készletet használ, ami terhelés alatt skálázható. Minden új IO-kérés létrehozhat egy további szálat a határ eléréséig.
Az egyidejű IO-műveletek számának szabályozásához használja a limitedParallelism() függvényt. Ez a függvény egy új diszpécsert hoz létre a párhuzamos szálak számának korlátozásával, megakadályozva a készlet kimerülését tömeges műveletek esetén.
val limitedIo = Dispatchers.IO.limitedParallelism(4)
// 100 fájl betöltése 4 egyidejű művelet korláttal
coroutineScope {
val files = (1..100).map { index ->
async(limitedIo) {
downloadFile("file_$index")
}
}
files.awaitAll()
}
Használja az IO-diszpécsert minden olyan művelethez, ahol a korutin várakozással tölti az időt (I/O-bound). A CPU-intenzív feladatok az IO-diszpécseren nem hatékonyak — olyan szálakat foglalnak le, amelyek a bemenet-kimenet számára lettek fenntartva, csökkentve a rendszer átviteli sebességét.
Dispatchers.Default — a processzort terhelő számítási műveletekhez való diszpécser: rendezés, szűrés, JSON elemzés (Moshi, Kotlinx Serialization), képfeldolgozás, számítások. A készlet mérete megegyezik a processzormagok számával (de legalább 2). Ez biztosítja a CPU maximális terhelését kontextusváltás nélkül.
suspend fun processData(input: List<RawRecord>): List<ProcessedRecord> {
return withContext(Dispatchers.Default) {
input
.parallelStream()
.map { transform(it) }
.toList()
}
}
Ne használja a Dispatchers.Default-ot IO-műveletekhez — ez blokkolja a CPU-készlet azon szálait, amelyek számítási feladatokat dolgozhatnának fel. Az IO és Default szétválasztása lehetővé teszi a rendszererőforrások optimális kihasználását: az IO-szálak a bemenet-kimenetre várnak, a CPU-szálak folyamatosan számításokkal vannak elfoglalva.
Dispatchers.Unconfined — egy speciális diszpécser, amely nem köti a korutint egyetlen készlethez sem. A korutin ugyanabban a szálban kezdi meg a végrehajtást, ahol a launch/async meghívásra került, és felfüggesztés után abban a szálban folytatódik, amely a resume-ot hívta. Ez a viselkedés olyan közbenső műveletekhez alkalmas, amelyek nem igényelnek rögzített kontextust.
fun main() = runBlocking {
launch(Dispatchers.Unconfined) {
println("Késleltetés előtt: ${Thread.currentThread().getName()}")
delay(500L)
println("Késleltetés után: ${Thread.currentThread().getName()}")
}
}
Production kódban a Dispatchers.Unconfined ritkán használatos. Fő esetek: könnyű transzformációk az adatok másik diszpécsernek való továbbítása előtt, valamint tesztek. Production terheléshez használjon explicit diszpécsereket — az Unconfined kiszámíthatatlan, mivel a végrehajtás szálja a resume implementációjától függ.
A diszpécser kiválasztását a feladat típusa határozza meg: UI műveletek → Main, I/O-bound → IO, CPU-bound → Default, közbenső → öröklés a scope-ból. Android esetén ajánlott a korutint azon a diszpécseren indítani, ahol a fő munka történik, és a UI frissítése előtt withContext segítségével Main-re váltani.
Összetett forgatókönyvekhez kombinálja a diszpécsereket a + operátorral: Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler. Ez egy CoroutineContext-et hoz létre a megadott diszpécserrel, hibakezeléssel és izolált Job-hierarchiával.
Gyakran Ismételt Kérdések
Dispatchers.IO akár 64 szálas készletet használ az I/O-bound műveletekhez (bemenet-kimenetre várás), míg Dispatchers.Default a CPU-magok számának megfelelő készletet a számítási feladatokhoz. Szálhiány esetén a két készlet megoszthatja egymással a szálakat.
Igen, használja a newSingleThreadContext() függvényt egyszálú vagy a newFixedThreadPoolContext() függvényt fix készlethez. Production esetén alkalmazza a limitedParallelism() függvényt meglévő diszpécserek alapján — ez hatékonyabb, mint új készletek létrehozása.
Ha a Dispatchers.Main nem érhető el (pl. JUnit tesztben vagy háttérszolgáltatásban), IllegalStateException dobódik. Tesztekhez használja a TestCoroutineDispatcher-t, háttérszolgáltatásokhoz a Dispatchers.IO vagy Default értéket.
Használja a Dispatchers.IO.limitedParallelism(N) függvényt, ahol N a maximális párhuzamos szálak száma. Ez megakadályozza a készlet kimerülését tömeges kérések esetén és ellenőrzött párhuzamosságot biztosít.
Dispatchers.Unconfined közbenső műveletekhez alkalmas: könnyű adat-transzformációk másik diszpécsernek való továbbítás előtt, tesztforgatókönyvek. Android production kódban nem ajánlott a felfüggesztés utáni bizonytalan végrehajtási szál miatt.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is