Dispatchers di Kotlin Coroutines — komponen CoroutineContext yang menentukan thread untuk mengeksekusi coroutine: Main (thread UI), IO (jaringan dan disk), Default (tugas intensif CPU) dan Unconfined (thread saat ini). Setiap dispatcher mengelola kumpulan thread khusus yang dioptimalkan untuk jenis pekerjaan tertentu. Menurut panduan JetBrains, 2024, pemilihan dispatcher yang tepat sangat penting untuk kinerja dan stabilitas aplikasi.
Poin Utama
Dispatchers — implementasi antarmuka CoroutineDispatcher, yang merupakan elemen CoroutineContext. Mereka menentukan di thread atau kumpulan thread mana coroutine akan dijalankan. Saat membuat coroutine melalui launch atau async, dispatcher dapat diberikan sebagai parameter pertama: launch(Dispatchers.IO) { ... }. Jika dispatcher tidak ditentukan, akan diwarisi dari CoroutineScope eksternal.
Kotlin menyediakan empat dispatcher bawaan: Main, IO, Default, Unconfined. Setiap dispatcher menggunakan kumpulan thread sendiri, dioptimalkan untuk jenis operasi tertentu. Pemilihan dispatcher yang tepat menentukan kinerja aplikasi: kesalahan pemilihan menyebabkan lag antarmuka, inti CPU menganggur, atau konsumsi thread yang tidak efisien.
| Dispatcher | Kumpulan thread | Maks. thread | Penggunaan |
|---|---|---|---|
| Dispatchers.Main | Satu (UI) | 1 | Pembaruan UI, LiveData, View |
| Dispatchers.IO | Kumpulan IO | 64 (limitedParallelism) | Jaringan, file, database |
| Dispatchers.Default | Kumpulan CPU | N inti | Pengurutan, parsing, kalkulasi |
| Dispatchers.Unconfined | Thread saat ini | T/A | Operasi perantara, pengujian |
Dispatchers.Main — dispatcher yang menjalankan coroutine di thread utama Android. Ini ditujukan untuk operasi yang terkait dengan antarmuka: memperbarui TextView, memanggil notifyDataSetChanged, bekerja dengan LiveData dan StateFlow. Di Android, dispatcher ini diimplementasikan melalui Handler (Looper.getMainLooper()).
// Peralihan yang benar ke Main untuk pembaruan UI
viewModelScope.launch(Dispatchers.IO) {
val data = repository.fetchData()
withContext(Dispatchers.Main) {
_uiState.value = data
}
}
Jika coroutine sudah berada di Main-dispatcher, withContext(Dispatchers.Main) tambahan tidak menimbulkan overhead — dispatcher memeriksa thread saat ini dan melewatkan perpindahan. withContext adalah cara yang lebih disukai untuk berpindah antar dispatcher.
Dispatchers.IO — dispatcher yang dioptimalkan untuk operasi input-output: permintaan HTTP (Ktor, OkHttp), membaca dan menulis file, bekerja dengan Room atau SQLDelight. Secara default menggunakan kumpulan 64 thread, dapat diskalakan di bawah beban. Setiap permintaan IO baru dapat membuat thread tambahan hingga batas tercapai.
Untuk mengontrol jumlah operasi IO simultan, gunakan limitedParallelism(). Fungsi ini membuat dispatcher baru dengan batasan jumlah thread paralel, mencegah habisnya kumpulan pada operasi massal.
val limitedIo = Dispatchers.IO.limitedParallelism(4)
// Muat 100 file dengan batas 4 operasi bersamaan
coroutineScope {
val files = (1..100).map { index ->
async(limitedIo) {
downloadFile("file_$index")
}
}
files.awaitAll()
}
Gunakan IO-dispatcher untuk semua operasi di mana coroutine menghabiskan waktu menunggu (I/O-bound). Tugas intensif CPU pada IO-dispatcher tidak efisien — mereka menempati thread yang ditujukan untuk input-output, mengurangi throughput sistem.
Dispatchers.Default — dispatcher untuk operasi komputasi yang membebani prosesor: pengurutan, pemfilteran, parsing JSON (Moshi, Kotlinx Serialization), pemrosesan gambar, kalkulasi. Ukuran kumpulan sama dengan jumlah inti prosesor (tetapi tidak kurang dari 2). Ini memastikan beban CPU maksimal tanpa perpindahan konteks.
suspend fun processData(input: List<RawRecord>): List<ProcessedRecord> {
return withContext(Dispatchers.Default) {
input
.parallelStream()
.map { transform(it) }
.toList()
}
}
Jangan gunakan Dispatchers.Default untuk operasi IO — ini akan memblokir thread kumpulan CPU yang bisa memproses tugas komputasi. Pemisahan menjadi IO dan Default memungkinkan pemanfaatan optimal sumber daya sistem: thread IO menunggu input-output, thread CPU terus sibuk dengan kalkulasi.
Dispatchers.Unconfined — dispatcher khusus yang tidak mengikat coroutine ke kumpulan mana pun. Coroutine memulai eksekusi di thread yang sama di mana launch/async dipanggil, dan setelah penangguhan dilanjutkan di thread yang memanggil resume. Perilaku ini cocok untuk operasi perantara yang tidak memerlukan konteks tetap.
fun main() = runBlocking {
launch(Dispatchers.Unconfined) {
println("Sebelum penundaan: ${Thread.currentThread().getName()}")
delay(500L)
println("Setelah penundaan: ${Thread.currentThread().getName()}")
}
}
Dalam kode produksi, Dispatchers.Unconfined jarang digunakan. Kasus utama: transformasi ringan sebelum mengirim data ke dispatcher lain dan pengujian. Untuk beban produksi, gunakan dispatcher eksplisit — Unconfined tidak dapat diprediksi karena thread eksekusi tergantung pada implementasi resume.
Pemilihan dispatcher ditentukan oleh jenis tugas: operasi UI → Main, I/O-bound → IO, CPU-bound → Default, perantara → warisan dari scope. Untuk Android, disarankan memulai coroutine pada dispatcher tempat pekerjaan utama dilakukan, dan sebelum pembaruan UI beralih ke Main melalui withContext.
Untuk skenario kompleks, gabungkan dispatcher dengan operator +: Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler. Ini membuat CoroutineContext dengan dispatcher yang ditentukan, penanganan kesalahan, dan hierarki Job yang terisolasi.
Pertanyaan Umum
Dispatchers.IO menggunakan kumpulan hingga 64 thread untuk operasi I/O-bound (menunggu input-output), dan Dispatchers.Default — kumpulan berdasarkan jumlah inti CPU untuk tugas komputasi. Saat thread tidak mencukupi, kedua kumpulan dapat berbagi thread satu sama lain.
Ya, gunakan newSingleThreadContext() untuk single-thread atau newFixedThreadPoolContext() untuk kumpulan tetap. Untuk produksi, gunakan limitedParallelism() berdasarkan dispatcher yang ada — ini lebih efisien daripada membuat kumpulan baru.
Jika Dispatchers.Main tidak tersedia (misalnya, dalam pengujian JUnit atau layanan latar belakang), IllegalStateException akan dilemparkan. Untuk pengujian, gunakan TestCoroutineDispatcher, untuk layanan latar belakang — Dispatchers.IO atau Default.
Gunakan Dispatchers.IO.limitedParallelism(N), di mana N adalah jumlah maksimum thread paralel. Ini mencegah habisnya kumpulan pada permintaan massal dan memberikan paralelisme yang terkendali.
Dispatchers.Unconfined cocok untuk operasi perantara: transformasi data ringan sebelum dikirim ke dispatcher lain, skenario pengujian. Dalam kode produksi Android tidak disarankan karena thread eksekusi yang tidak pasti setelah penangguhan.
Ringkasan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga