Dispatchers sa Kotlin Coroutines — mga bahagi ng CoroutineContext na tumutukoy sa mga thread para sa pagpapatupad ng mga coroutine: Main (UI-thread), IO (network at disk), Default (mga gawaing CPU-intensive) at Unconfined (kasalukuyang thread). Ang bawat dispatcher ay namamahala ng espesyalisadong pool ng thread, na na-optimize para sa partikular na uri ng trabaho. Ayon sa gabay ng JetBrains, 2024, ang pagpili ng tamang dispatcher ay kritikal para sa pagganap at katatagan ng aplikasyon.
Mga Pangunahing Punto
Dispatchers — mga implementasyon ng interface ng CoroutineDispatcher, na mga elemento ng CoroutineContext. Tinutukoy nila kung saang thread o pool ng thread isasagawa ang coroutine. Kapag lumilikha ng coroutine sa pamamagitan ng launch o async, ang dispatcher ay maaaring ipasa bilang unang parameter: launch(Dispatchers.IO) { ... }. Kung hindi tinukoy ang dispatcher, ito ay minamana mula sa panlabas na CoroutineScope.
Nagbibigay ang Kotlin ng apat na built-in na dispatcher: Main, IO, Default, Unconfined. Ang bawat dispatcher ay gumagamit ng sarili nitong pool ng thread, na na-optimize para sa partikular na uri ng operasyon. Ang tamang pagpili ng dispatcher ay tumutukoy sa pagganap ng aplikasyon: ang maling pagpili ay humahantong sa lag ng interface, idle CPU cores, o hindi mahusay na paggamit ng thread.
| Dispatcher | Pool ng thread | Max. thread | Paggamit |
|---|---|---|---|
| Dispatchers.Main | Isa (UI) | 1 | Pag-update ng UI, LiveData, View |
| Dispatchers.IO | IO pool | 64 (limitedParallelism) | Network, mga file, database |
| Dispatchers.Default | CPU pool | N core | Pag-uuri, parsing, pag-compute |
| Dispatchers.Unconfined | Kasalukuyang thread | WALA | Mga operasyong pansamantala, pagsubok |
Dispatchers.Main — dispatcher na nagpapatupad ng mga coroutine sa pangunahing thread ng Android. Ito ay inilaan para sa mga operasyong nauugnay sa interface: pag-update ng TextView, pagtawag ng notifyDataSetChanged, pagtatrabaho sa LiveData at StateFlow. Sa Android, ang dispatcher na ito ay ipinatupad sa pamamagitan ng Handler (Looper.getMainLooper()).
// Tamang paglipat sa Main para sa mga update ng UI
viewModelScope.launch(Dispatchers.IO) {
val data = repository.fetchData()
withContext(Dispatchers.Main) {
_uiState.value = data
}
}
Kung ang coroutine ay nasa Main-dispatcher na, ang karagdagang withContext(Dispatchers.Main) ay hindi lumilikha ng overhead — sinusuri ng dispatcher ang kasalukuyang thread at nilalaktawan ang paglipat. withContext ang gustong paraan ng paglipat sa pagitan ng mga dispatcher.
Dispatchers.IO — dispatcher na na-optimize para sa input-output operations: mga kahilingang HTTP (Ktor, OkHttp), pagbasa at pagsulat ng mga file, pagtatrabaho sa Room o SQLDelight. Ito ay gumagamit ng pool ng 64 na thread bilang default, nasusukat sa ilalim ng load. Ang bawat bagong kahilingang IO ay maaaring lumikha ng karagdagang thread hanggang maabot ang limitasyon.
Upang kontrolin ang bilang ng sabay-sabay na IO operations, gamitin ang limitedParallelism(). Ang function na ito ay lumilikha ng bagong dispatcher na may limitasyon sa bilang ng parallel thread, na pumipigil sa pagkaubos ng pool sa mga malawakang operasyon.
val limitedIo = Dispatchers.IO.limitedParallelism(4)
// Mag-load ng 100 file na may limit na 4 na sabay-sabay na operasyon
coroutineScope {
val files = (1..100).map { index ->
async(limitedIo) {
downloadFile("file_$index")
}
}
files.awaitAll()
}
Gamitin ang IO-dispatcher para sa lahat ng operasyon kung saan ang coroutine ay gumugugol ng oras sa paghihintay (I/O-bound). Ang mga gawaing CPU-intensive sa IO-dispatcher ay hindi epektibo — sinasakop nila ang mga thread na para sa input-output, binabawasan ang throughput ng sistema.
Dispatchers.Default — dispatcher para sa mga operasyong pangkalkulasyon na nagbibigay-diin sa processor: pag-uuri, pag-filter, parsing ng JSON (Moshi, Kotlinx Serialization), pagproseso ng imahe, pag-compute. Ang laki ng pool ay katumbas ng bilang ng mga core ng processor (ngunit hindi bababa sa 2). Tinitiyak nito ang maximum na pag-load ng CPU nang walang paglipat ng konteksto.
suspend fun processData(input: List<RawRecord>): List<ProcessedRecord> {
return withContext(Dispatchers.Default) {
input
.parallelStream()
.map { transform(it) }
.toList()
}
}
Huwag gamitin ang Dispatchers.Default para sa IO operations — haharangin nito ang mga thread ng CPU pool na maaaring magproseso ng mga gawaing pangkalkulasyon. Ang paghihiwalay sa IO at Default ay nagbibigay-daan sa optimal na paggamit ng mga mapagkukunan ng sistema: ang IO thread ay naghihintay ng input-output, ang CPU thread ay palaging abala sa pag-compute.
Dispatchers.Unconfined — isang espesyal na dispatcher na hindi nagtatali ng coroutine sa anumang pool. Sinisimulan ng coroutine ang pagpapatupad sa parehong thread kung saan tinawag ang launch/async, at pagkatapos ng pagsususpinde ay magpapatuloy sa thread na tumawag sa resume. Ang ganitong pag-uugali ay angkop para sa mga pansamantalang operasyon na hindi nangangailangan ng nakapirming konteksto.
fun main() = runBlocking {
launch(Dispatchers.Unconfined) {
println("Bago ang pagkaantala: ${Thread.currentThread().getName()}")
delay(500L)
println("Pagkatapos ng pagkaantala: ${Thread.currentThread().getName()}")
}
}
Sa production code, ang Dispatchers.Unconfined ay bihirang ginagamit. Mga pangunahing kaso: magaan na pagbabago bago ipadala ang data sa ibang dispatcher at pagsubok. Para sa production load, gumamit ng tahasang dispatcher — ang Unconfined ay hindi mahuhulaan dahil ang thread ng pagpapatupad ay nakadepende sa implementasyon ng resume.
Ang pagpili ng dispatcher ay tinutukoy ng uri ng gawain: UI operations → Main, I/O-bound → IO, CPU-bound → Default, pansamantala → pagmamana mula sa scope. Para sa Android, inirerekomenda na simulan ang coroutine sa dispatcher kung saan ginagawa ang pangunahing trabaho, at bago ang pag-update ng UI, lumipat sa Main sa pamamagitan ng withContext.
Para sa mga kumplikadong senaryo, pagsamahin ang mga dispatcher gamit ang operator na +: Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler. Lumilikha ito ng CoroutineContext na may tinukoy na dispatcher, paghawak ng error, at nakahiwalay na hierarchy ng Job.
Mga Madalas Itanong
Dispatchers.IO ay gumagamit ng pool hanggang 64 na thread para sa I/O-bound operations (paghihintay ng input-output), at Dispatchers.Default — pool ayon sa bilang ng CPU cores para sa mga gawaing pangkalkulasyon. Kapag kulang ang thread, ang parehong pool ay maaaring magbahagi ng thread sa isa't isa.
Oo, gamitin ang newSingleThreadContext() para sa single-thread o newFixedThreadPoolContext() para sa nakapirming pool. Para sa produksyon, gamitin ang limitedParallelism() batay sa mga umiiral na dispatcher — ito ay mas epektibo kaysa sa paggawa ng mga bagong pool.
Kung hindi available ang Dispatchers.Main (halimbawa, sa JUnit test o background service), itatapon ang IllegalStateException. Para sa pagsubok, gamitin ang TestCoroutineDispatcher, para sa background services — Dispatchers.IO o Default.
Gamitin ang Dispatchers.IO.limitedParallelism(N), kung saan N ang maximum na bilang ng parallel thread. Pinipigilan nito ang pagkaubos ng pool sa malawakang kahilingan at nagbibigay ng kontroladong paralelismo.
Dispatchers.Unconfined ay angkop para sa mga pansamantalang operasyon: magaan na pagbabago ng data bago ipadala sa ibang dispatcher, mga senaryo ng pagsubok. Sa production code ng Android ay hindi inirerekomenda dahil sa hindi tiyak na thread ng pagpapatupad pagkatapos ng pagsususpinde.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din