Dispatcher

Default, IO, dan Main beserta penempatan thread.

Daftar isi

Dispatcher adalah bagian dari konteks coroutine yang menentukan thread mana yang menjalankan kode coroutine, dan karenanya menjadi tempat keputusan penempatan pekerjaan diambil. Topik ini menguraikan dispatcher bawaan Dispatchers.Default, Dispatchers.IO, dan Dispatchers.Main, menunjukkan perpindahan konteks dengan withContext, serta menutup dengan praktik penempatan pekerjaan pada platform berantarmuka grafis.

Dispatcher bawaan#

Library coroutines menyediakan tiga dispatcher bawaan yang masing-masing dirancang untuk jenis pekerjaan tertentu. Dispatchers.Default berisi pool thread yang ukurannya mengikuti jumlah inti prosesor dan ditujukan untuk pekerjaan berhitung berat seperti pengurutan data besar atau pengolahan gambar. Dispatchers.IO berisi pool yang lentur dengan jumlah thread yang lebih banyak dan ditujukan untuk pekerjaan yang menunggu, misalnya membaca berkas atau memanggil jaringan, karena thread pada pekerjaan semacam itu sering diam menunggu.

Dispatcher ketiga, Dispatchers.Main, menempatkan coroutine pada thread antarmuka pengguna. Dispatcher ini tidak ada pada JVM polos dan baru tersedia setelah library integrasi platformnya disertakan, misalnya pada aplikasi Android atau aplikasi desktop berbasis JavaFX. Karena itu kode yang merujuk Dispatchers.Main hanya dapat dijalankan pada platform yang menyediakannya, dan contoh pada topik ini tidak menjalankannya di terminal.

DispatcherIsi poolPekerjaan yang cocok
Dispatchers.DefaultThread sebanyak inti prosesorPerhitungan berat, pengurutan, transformasi data
Dispatchers.IOPool lentur dengan thread yang lebih banyakBerkas, jaringan, panggilan yang menunggu
Dispatchers.MainThread antarmuka penggunaPembaruan layar, hanya pada platform UI

Pemisahan dua pool pertama mencegah kedua jenis pekerjaan saling merebut thread. Pool Default yang kecil menjaga perhitungan berat tetap memakai seluruh inti prosesor secara efisien, sedangkan pool IO yang besar menampung banyak thread yang sibuk menunggu. Apabila keduanya digabung, pekerjaan menunggu bisa menghabiskan thread yang dibutuhkan perhitungan, atau sebaliknya perhitungan berat menyita thread untuk pemrosesan masukan.

Perpindahan konteks dengan withContext#

Fungsi withContext menjeda coroutine yang sedang berjalan, menjalankan bloknya pada dispatcher yang dituju, lalu mengembalikan eksekusi ke konteks sebelumnya dengan seluruh hasil blok sebagai nilai kembaliannya. Perpindahan itu terjadi tanpa membuat coroutine baru, sehingga keadaan variabel lokal tetap utuh dan kode tetap terbaca sekuensial.

Program berikut membuktikan perpindahan itu dengan thread yang diberi nama sendiri. Thread pemberi nama sendiri dipilih agar keluarannya stabil, karena nama thread milik pool bawaan berubah antarjalankan dan tidak layak dijadikan acuan.

kotlin main.kt
import kotlinx.coroutines.*
import java.util.concurrent.Executors

fun main() = runBlocking {
    val dispatcherLaporan = Executors.newSingleThreadExecutor { tugas ->
        Thread(tugas, "ctx-laporan-1")
    }.asCoroutineDispatcher()

    println("Awal pada thread: ${Thread.currentThread().name}")
    withContext(dispatcherLaporan) {
        println("Memuat laporan pada thread: ${Thread.currentThread().name}")
    }
    println("Kembali pada thread: ${Thread.currentThread().name}")

    dispatcherLaporan.close()
}
bash
# compiler Kotlin dan library coroutines yang dipakai jalur ini
export PATH=/home/kal/my-project/tools/kotlinc/bin:/home/kal/my-project/tools/jdk-merged/bin:$PATH
export LIBS=/home/kal/my-project/tools/libs
export STDLIB=/home/kal/my-project/tools/kotlinc/lib/kotlin-stdlib.jar
# kompilasi main.kt dengan library coroutines pada classpath, lalu jalankan programnya
kotlinc main.kt -cp "$LIBS/kotlinx-coroutines-core-jvm-1.11.0.jar" -d out.jar && java -cp "out.jar:$LIBS/kotlinx-coroutines-core-jvm-1.11.0.jar:$STDLIB" MainKt
text Keluaran
Awal pada thread: main
Memuat laporan pada thread: ctx-laporan-1
Kembali pada thread: main

Keluarannya mengikuti jalur perpindahan dengan tepat. runBlocking menjalankan isinya pada thread main, kemudian blok withContext dieksekusi pada thread ctx-laporan-1 milik executor tunggal, dan setelah blok selesai eksekusi kembali ke thread main. runBlocking memang jembatan yang berjalan di thread main, sedangkan coroutine di dalamnya dapat berpindah thread berkali-kali tanpa menyadari perpindahan itu.

Dispatchers bawaan dapat dipakai dengan pola yang sama, misalnya withContext(Dispatchers.IO) { ... } di sekitar pembacaan berkas. Bedanya, nama thread milik pool bawaan tidak stabil untuk dicetak, sehingga bukti perpindahan pada contoh ini sengaja memakai executor tunggal bernama.

Penempatan pekerjaan pada platform UI#

Pada aplikasi berantarmuka grafis, keputusan penempatan pekerjaan mengikuti satu aturan, yaitu pembaruan layar hanya boleh dilakukan dari thread UI, sedangkan pekerjaan yang menunggu ditempatkan di tempat lain. Pola yang lazim adalah memuat data pada Dispatchers.IO dan menampilkan hasilnya pada Dispatchers.Main, sebagaimana pada potongan kode berikut.

kotlin Contoh: perpindahan IO ke Main pada aplikasi Android
// tidak-dijalankan
// Dispatchers.Main hanya tersedia pada platform UI, misalnya Android
suspend fun tampilkanLaporan() {
    val data = withContext(Dispatchers.IO) {
        muatLaporan()
    }
    withContext(Dispatchers.Main) {
        tampilkan(data)
    }
}

Pola tersebut terbaca sebagai satu urutan lurus, padahal di baliknya coroutine berpindah thread dua kali, dari thread UI ke pool IO lalu kembali ke thread UI. Selama proses memuat, thread UI tetap bebas menggambar layar dan menanggapi sentuhan, sehingga antarmuka tidak pernah macet. Pola inilah alasan utama dispatcher dipisah-pisah, yaitu menjaga thread yang kritis tetap ringan.

Ringkasan#

  • Dispatcher menentukan thread yang menjalankan coroutine, dan pilihan pemakaiannya adalah keputusan penempatan pekerjaan.
  • Dispatchers.Default berisi thread sebanyak inti prosesor untuk perhitungan berat, Dispatchers.IO berisi pool lentur untuk pekerjaan menunggu, sedangkan Dispatchers.Main hanya ada pada platform UI.
  • withContext menjalankan blok pada dispatcher tujuan lalu kembali ke konteks semula dengan nilai kembalian bloknya.
  • Pada platform UI, data dimuat pada Dispatchers.IO dan hasilnya ditampilkan pada Dispatchers.Main agar thread UI tetap bebas.

Referensi#