Kata kunci suspend menandai fungsi yang boleh dijeda di tengah eksekusi dan dilanjutkan kembali dari titik yang sama tanpa memblokir thread. Topik ini membahas cara mendeklarasikan fungsi suspend, mekanisme titik suspensi (suspension point) sebagai tempat pejeda terjadi, serta gaya penulisan kode asynchronous yang tetap terbaca seperti kode sekuensial biasa.
Deklarasi fungsi suspend#
Sebuah fungsi menjadi fungsi suspend dengan menambahkan modifier suspend pada deklarasinya. Di dalamnya, pemanggilan fungsi suspend lain seperti delay boleh ditulis persis seperti pemanggilan fungsi biasa. Modifier ini ditulis pada deklarasi, sehingga seluruh pemanggilnya langsung mengetahui bahwa fungsi tersebut dapat dijeda di tengah eksekusinya. Program berikut mendeklarasikan dua fungsi suspend yang masing-masing menunggu 100 milidetik sebelum mengirim hasilnya, lalu memanggil keduanya secara berurutan dari main.
Pola semacam ini muncul di banyak pustaka coroutine nyata. Fungsi yang membungkus pembacaan berkas, panggilan jaringan, atau kueri basis data lazim diberi modifier suspend, karena pekerjaan tersebut pada dasarnya menunggu dan menunggu adalah tempat pejeda paling bermanfaat. Dengan penanda itu, pemanggil juga dibebaskan dari keharusan mengelola thread tambahan, sebab pejeda pada fungsi suspend tidak menahan thread apa pun.
import kotlinx.coroutines.*
suspend fun muatLaporan(): String {
delay(100)
return "laporan-penjualan"
}
suspend fun hitungStatistik(): String {
delay(100)
return "statistik-2026"
}
fun main() = runBlocking {
println("Mulai memproses")
val laporan = muatLaporan()
println("Laporan dimuat: $laporan")
val statistik = hitungStatistik()
println("Statistik dihitung: $statistik")
println("Pemrosesan selesai")
}# 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" MainKtMulai memproses
Laporan dimuat: laporan-penjualan
Statistik dihitung: statistik-2026
Pemrosesan selesaiKeluarannya memperlihatkan urutan yang terjaga dari atas ke bawah. Fungsi hitungStatistik tidak mulai sebelum muatLaporan mengembalikan hasilnya, karena pemanggilan suspend yang ditulis berurutan memang dieksekusi berurutan. Gaya semacam ini disebut gaya sekuensial pada kode asynchronous (asynchronous), karena alurnya lurus dari atas ke bawah tanpa callback maupun rantai fungsi bersarang.
Titik suspensi#
Titik suspensi adalah posisi pemanggilan fungsi suspend, dan itulah satu-satunya tempat sebuah coroutine boleh dijeda. Di antara dua titik suspensi, kode berjalan seperti kode biasa, yaitu tanpa peluang bagi coroutine lain untuk menyisip di tengahnya. Ketika coroutine mencapai titik suspensi dan hasil yang ditunggu belum siap, kondisi eksekusinya disimpan, thread dibebaskan, lalu eksekusi dilanjutkan dari titik tersebut begitu hasilnya datang.
Sifat sekuensial tersebut dapat diukur langsung. Program berikut merangkai dua fungsi suspend yang masing-masing menunggu 150 milidetik, lalu mengukur total waktunya. Pembacaan waktu dibulatkan ke ratusan milidetik pada pernyataan cetak agar angkanya stabil antarjalankan program.
import kotlinx.coroutines.*
suspend fun unduhData(): String {
delay(150)
return "arsip-cadangan"
}
suspend fun olahData(data: String): String {
delay(150)
return "indeks-$data"
}
fun main() = runBlocking {
val awal = System.nanoTime()
val hasil = olahData(unduhData())
val durasiMs = (System.nanoTime() - awal) / 1_000_000
println("Hasil: $hasil")
println("Waktu total sekitar ${durasiMs / 100 * 100} ms")
}# kompilasi waktu.kt dengan library coroutines pada classpath, lalu jalankan programnya
kotlinc waktu.kt -cp "$LIBS/kotlinx-coroutines-core-jvm-1.11.0.jar" -d waktu.jar && java -cp "waktu.jar:$LIBS/kotlinx-coroutines-core-jvm-1.11.0.jar:$STDLIB" WaktuKtHasil: indeks-arsip-cadangan
Waktu total sekitar 300 msTotalnya sekitar 300 milidetik, yaitu 150 ditambah 150, karena pemanggilan berurutan menumpuk waktunya. Apabila kedua pekerjaan tidak saling bergantung, keduanya dapat dijalankan bersamaan dengan async sehingga totalnya mendekati 150 milidetik. Dengan demikian, keputusan untuk menjalankan pekerjaan secara berurutan atau bersamaan terletak pada pola pemanggilannya, sedangkan bentuk kodenya tetap sama lurus.
Perbandingan dengan Thread.sleep menegaskan perbedaan pejeda dan pemblokiran. Fungsi delay adalah fungsi suspend, sehingga saat coroutine menunggu, thread yang menjalankannya bebas mengerjakan coroutine lain. Fungsi Thread.sleep memblokir thread sepenuhnya dan tidak memberi peluang apa pun kepada coroutine lain, sehingga kombinasi delay dan titik suspensi adalah cara yang benar untuk menunggu di dalam coroutine.
Aturan pemanggilan fungsi suspend#
Fungsi suspend hanya boleh dipanggil dari fungsi suspend lain atau dari dalam coroutine, misalnya di dalam runBlocking, launch, maupun async. Compiler menegakkan aturan ini pada saat kompilasi, sehingga pemanggilan dari fungsi biasa ditolak dengan pesan error sebelum program sempat berjalan. Aturan itu bukan formalitas, melainkan konsekuensi dari mekanismenya, karena fungsi suspend membutuhkan objek continuation untuk menyimpan dan melanjutkan eksekusinya, dan objek itu baru tersedia di dalam dunia coroutine.
Di balik layar, compiler mengubah fungsi suspend menjadi fungsi biasa dengan parameter tambahan berupa continuation tersebut. Transformasi inilah yang membuat pejeda mungkin terjadi, sekaligus menjelaskan mengapa modifier suspend perlu ditulis secara eksplisit, yaitu agar tanda tangan fungsi mencerminkan kemampuannya untuk dijeda.
Kesalahan umum#
Kesalahan yang menipu adalah membungkus pemanggilan yang memblokir ke dalam fungsi suspend tanpa memindahkan threadnya. Modifier suspend pada deklarasi tidak otomatis mengubah perilaku fungsi, sehingga fungsi seperti berikut tetap memblokir thread sepenuhnya selama pembacaan berkasnya berlangsung.
// tidak-dijalankan
// pemanggilan yang memblokir dibungkus suspend tanpa perpindahan dispatcher
suspend fun bacaKonfigurasi(): String {
val isi = File("konfigurasi.txt").readText()
return isi.trim()
}Penandanya cocok dengan perilakunya di permukaan, tetapi thread yang menjalankan fungsi tersebut tetap tertahan sampai pembacaan berkas selesai, dan peluang coroutine lain untuk berjalan di thread itu ikut hilang. Perbaikannya adalah memindahkan pemanggilan yang memblokir ke dispatcher yang menampung pekerjaan semacam itu dengan withContext, misalnya Dispatchers.IO, sehingga thread pemanggil tetap bebas. Cara lain yang lebih menyeluruh adalah mengganti pemanggilan yang memblokir dengan versi suspend, misalnya API berkas yang memang dirancang untuk coroutine.
Ringkasan#
- Modifier
suspendmenandai fungsi yang boleh dijeda dan dilanjutkan dari titik yang sama, dan fungsi semacam itu hanya dapat dipanggil dari fungsi suspend lain atau dari dalam coroutine. - Titik suspensi adalah pemanggilan fungsi suspend, dan di antara dua titik suspensi kode berjalan sekuensial tanpa penyisipan.
- Pemanggilan suspend yang ditulis berurutan menumpuk total waktunya, sebagaimana terukur 300 milidetik pada contoh dua pekerjaan 150 milidetik.
delaymenjeda coroutine dan membebaskan thread, sedangkanThread.sleepmemblokir thread sepenuhnya.
Referensi#
- Coroutines basics — JetBrains — Dokumentasi resmi fungsi suspend, delay, dan penjelasan cara kerja suspensi.
- Coroutines guide — JetBrains — Panduan resmi coroutines Kotlin sebagai peta seluruh topik pada modul ini.