CoroutineScope — apa itu, cakupan hidup dan kerja dalam coroutine

Penulis: IT Sectr Diterbitkan: 2026-06-22 Waktu membaca: 10 mnt

CoroutineScope — adalah antarmuka Kotlin yang menentukan cakupan hidup coroutine dan menyediakan konteks untuk meluncurkan coroutine baru. Menurut dokumentasi Kotlin, 2025, setiap instance CoroutineScope berisi CoroutineContext dan mengelola semua coroutine yang diluncurkan di dalamnya. Ketika scope berakhir (cancel), semua coroutine anak secara otomatis dibatalkan, mencegah kebocoran memori.

Poin Penting

  • CoroutineScope — antarmuka dengan satu bidang CoroutineContext, yang menentukan siklus hidup coroutine
  • Job — elemen konteks yang bertanggung jawab untuk pembatalan: pembatalan scope membatalkan semua coroutine anak
  • Konkurensi struktural — prinsip di mana coroutine anak terikat pada scope induk
  • GlobalScope — scope untuk seluruh aplikasi, tidak direkomendasikan karena risiko kebocoran
  • supervisorScope — scope khusus di mana pembatalan satu coroutine anak tidak membatalkan yang lain

Apa itu CoroutineScope di Kotlin?

CoroutineScope — adalah antarmuka fundamental dari pustaka kotlinx.coroutines yang berfungsi sebagai wadah untuk coroutine. Ini menentukan batas hidup coroutine: ketika scope berakhir, semua coroutine di dalamnya secara otomatis dibatalkan.

kotlin
public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

Antarmuka hanya berisi satu bidang — coroutineContext. Melaluinya, scope menyediakan dispatcher (Dispatcher), tugas (Job), penanganan pengecualian, dan elemen konteks lainnya untuk semua coroutine yang diluncurkan di dalamnya.

Peran dalam pustaka kotlinx.coroutines

Semua fungsi peluncuran coroutine — launch, async, runBlocking — adalah fungsi ekstensi pada CoroutineScope. Ini berarti mereka hanya dapat dipanggil jika ada objek scope. Desain semacam itu menjamin bahwa setiap coroutine memiliki induk dan siklus hidup yang ditentukan dengan jelas.

Di mana CoroutineScope diterapkan

Di Android, setiap komponen arsitektur memiliki scope sendiri: viewModelScope untuk ViewModel, lifecycleScope untuk Activity/Fragment. Di aplikasi server, scope dapat dikaitkan dengan permintaan HTTP atau dengan kumpulan koneksi basis data.

Bagaimana Cara Kerja CoroutineScope: Job dan Konkurensi Struktural

Memahami struktur internal CoroutineScope memerlukan keakraban dengan konsep Job dan prinsip konkurensi struktural.

Job — Tugas Coroutine

Setiap coroutine saat diluncurkan mengembalikan objek Job (atau Deferred untuk async). Job mewakili tugas dengan siklus hidup yang ditentukan: New, Active, Completing, Completed, Cancelling, Cancelled. Objek Job membentuk struktur pohon:

  • Job Induk — scope tempat coroutine diluncurkan
  • Job Anak — setiap coroutine yang diluncurkan melalui launch/async
  • Pembatalan induk → pembatalan semua anak
  • Pengecualian pada anak → pembatalan induk (kecuali supervisorScope)

Prinsip Konkurensi Struktural

Konkurensi struktural — prinsip arsitektur utama Kotlin Coroutines, di mana masa hidup coroutine terikat pada masa hidup scope-nya. Ini kontras dengan model “fire-and-forget”, di mana coroutine terus hidup setelah scope berakhir. Keuntungan konkurensi struktural:

  • Siklus hidup yang dapat diprediksi — ketika scope berakhir, semua coroutine dijamin berhenti
  • Penanganan kesalahan otomatis — pengecualian pada coroutine anak mana pun menyebar ke scope
  • Tidak ada kebocoran — tidak ada coroutine yang tetap aktif setelah scope berakhir
  • Hierarki yang jelas — kode mencerminkan struktur logis dari operasi paralel

Siklus Hidup CoroutineScope

Ketika scope.cancel() dipanggil, Job scope masuk ke status Cancelled, yang secara rekursif membatalkan semua Job anak. Setelah pembatalan, scope hanya dapat digunakan kembali dengan membuat instance CoroutineScope baru.

Membuat dan Mengonfigurasi CoroutineScope

CoroutineScope dapat dibuat melalui fungsi pabrik atau melalui implementasi antarmuka di kelas Anda. Mari kita bahas kedua pendekatan.

Fungsi Pabrik CoroutineScope()

kotlin
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())

scope.launch {
    println("Berjalan di ${Thread.currentThread().name}")
}

Fungsi pabrik menerima CoroutineContext dan membuat scope dengan konteks yang ditentukan. Dalam contoh, digunakan Dispatchers.Default untuk tugas intensif CPU dan SupervisorJob yang mengisolasi pengecualian antara coroutine anak.

Implementasi Antarmuka melalui Komposisi

kotlin
class MyRepository {
    private val scope = CoroutineScope(Dispatchers.IO + Job())

    suspend fun fetchData(): Data = scope.async {
        api.getData()
    }.await()

    fun cleanup() {
        scope.cancel()
    }
}

Kami menyimpan scope sebagai bidang kelas dan secara manual memanggil cleanup untuk membatalkannya. Ini cocok untuk komponen dengan siklus hidup terkelola — misalnya, untuk repositori atau manajer.

Implementasi melalui Delegasi

Kotlin memungkinkan delegasi implementasi CoroutineScope melalui kata kunci by:

kotlin
class DataLoader : CoroutineScope by CoroutineScope(Dispatchers.IO) {
    fun load() {
        launch {
            // coroutine berjalan di scope DataLoader
        }
    }
}

Pendekatan ini nyaman ketika kelas itu sendiri adalah scope dan ingin menyediakan metode peluncuran coroutine. Namun berhati-hatilah: kelas mewarisi semua metode CoroutineScope, termasuk cancel, yang dapat melanggar enkapsulasi.

GlobalScope vs CoroutineScope Kustom

GlobalScope — adalah singleton CoroutineScope untuk seluruh aplikasi. Penggunaannya dalam kode produksi secara resmi tidak direkomendasikan.

Masalah GlobalScope

  • Kurangnya konkurensi struktural — coroutine di GlobalScope tidak terikat pada siklus hidup komponen
  • Kebocoran memori — coroutine dapat terus berjalan setelah Activity/Fragment ditutup
  • Pengujian sulit — GlobalScope tidak dapat diganti dalam pengujian
  • Konsumsi sumber daya tidak terkendali — banyak coroutine dapat bekerja lebih lama dari yang diharapkan

Kapan GlobalScope Dibenarkan

JetBrains mengizinkan GlobalScope hanya dalam skenario langka: proses latar belakang tingkat aplikasi yang harus tetap hidup bahkan setelah semua Activity ditutup (misalnya, sinkronisasi data, analitik). Tetapi bahkan dalam kasus ini, lebih baik membuat scope sendiri dengan CoroutineScope(SupervisorJob()).

Rekomendasi

Selalu gunakan CoroutineScope kustom dengan manajemen siklus hidup yang eksplisit. Di Android, ini adalah viewModelScope dan lifecycleScope. Di aplikasi server, buat scope untuk setiap permintaan atau kumpulan koneksi.

coroutineScope vs supervisorScope: Apa Perbedaannya

Kedua fungsi adalah fungsi suspend yang membuat scope sementara untuk tugas paralel, tetapi perilaku mereka saat pengecualian pada dasarnya berbeda.

KarakteristikcoroutineScopesupervisorScope
Perilaku saat kesalahanPengecualian di coroutine anak membatalkan semua yang lainPengecualian di coroutine anak TIDAK membatalkan yang lain
Penyebaran kesalahanYa, pengecualian pertama disebarkan ke luarYa, pengecualian pertama disebarkan ke luar
Job defaultJob() — anak terikat pada indukSupervisorJob() — anak tidak bergantung satu sama lain
Use-case tipikalOperasi atom dari beberapa langkahTugas paralel independen (muatan UI)

Kapan Memilih coroutineScope

Gunakan coroutineScope ketika beberapa operasi paralel membentuk satu operasi atom. Misalnya, memuat data dari tiga server: jika satu permintaan gagal, yang lain tidak berarti.

kotlin
suspend fun loadProductPage(): ProductPage = coroutineScope {
    val product = async { api.getProduct() }
    val reviews = async { api.getReviews() }
    ProductPage(product.await(), reviews.await())
}

Jika getProduct atau getReviews melempar pengecualian — kedua coroutine dibatalkan, dan pengecualian disebarkan ke kode pemanggil.

Kapan Memilih supervisorScope

Gunakan supervisorScope ketika operasi paralel tidak bergantung satu sama lain. Misalnya, memuat data profil di beberapa bagian independen: jika bagian rekomendasi gagal, tajuk profil dan daftar teman harus ditampilkan.

Kesalahan Umum saat Bekerja dengan CoroutineScope

Mari kita bahas kesalahan paling umum pengembang saat menggunakan CoroutineScope di Kotlin.

Kesalahan 1: Lupa Membatalkan Scope

Skenario kebocoran coroutine yang paling umum — membuat scope tanpa memanggil cancel saat komponen berakhir. Jika scope tidak dibatalkan, coroutine terus berjalan, menyimpan referensi ke objek. Di Android, gunakan viewModelScope atau lifecycleScope, yang dibatalkan secara otomatis.

Kesalahan 2: Menggunakan GlobalScope di Activity atau Fragment

GlobalScope mengabaikan siklus hidup komponen Android. Coroutine yang diluncurkan di GlobalScope setelah Activity ditutup akan terus berjalan dan mencoba memperbarui UI — yang menyebabkan crash. Selalu gunakan lifecycleScope untuk komponen UI.

Kesalahan 3: Menggunakan Kembali Scope yang Sudah Dibatalkan

Setelah memanggil cancel(), scope tidak dapat digunakan kembali — semua coroutine di dalamnya sudah selesai. Buat instance CoroutineScope baru melalui fungsi pabrik. Job() tidak mendukung aktivasi ulang.

Kesalahan 4: Delegasi Antarmuka CoroutineScope yang Salah

Saat delegasi melalui by, kelas mendapatkan metode publik cancel() yang dapat dipanggil dari mana saja, melanggar enkapsulasi. Simpan scope sebagai bidang privat, jangan delegasikan antarmuka.

Pertanyaan yang Sering Diajukan

Apa perbedaan CoroutineScope dengan CoroutineContext?

CoroutineScope — adalah antarmuka yang memiliki CoroutineContext dan bertanggung jawab atas siklus hidup coroutine. CoroutineContext — adalah kumpulan elemen (dispatcher, job, penanganan kesalahan) yang menentukan “bagaimana” coroutine dijalankan. Salah satu perbedaannya: scope membuat coroutine, context mengelola perilaku mereka.

Bisakah CoroutineScope dibuat dengan SupervisorJob?

Ya, ini adalah pola standar: CoroutineScope(Dispatchers.IO + SupervisorJob()). SupervisorJob mencegah pembatalan berantai coroutine anak saat pengecualian di salah satunya. Ini berguna untuk tugas paralel independen di mana kesalahan pada satu tidak boleh menghentikan yang lain.

Berapa banyak coroutine yang dapat ditampung oleh CoroutineScope?

Tidak ada batasan jumlah coroutine dalam scope — mereka hanya dibatasi oleh memori yang tersedia dan pengaturan dispatcher. Batas praktis biasanya ribuan coroutine aktif dalam satu scope. Namun, jumlah coroutine yang besar dapat menunjukkan masalah arsitektur.

Bagaimana cara menguji kode dengan CoroutineScope?

Cara yang benar adalah meneruskan scope ke kelas melalui konstruktor atau menggunakan runBlockingTest / runTest dari kotlinx-coroutines-test. Dalam pengujian, Anda dapat mengganti scope dengan TestCoroutineDispatcher dan mengontrol eksekusi coroutine secara manual.

Bisakah satu coroutine memiliki scope sendiri?

Tidak, scope adalah wadah eksternal untuk coroutine. Coroutine itu sendiri bukan scope. Namun, di dalam coroutine, scope baru dapat dibuat melalui coroutineScope atau supervisorScope untuk meluncurkan coroutine anak secara paralel.

Ringkasan

  • CoroutineScope — antarmuka dengan bidang coroutineContext, yang menentukan siklus hidup coroutine yang diluncurkan di dalamnya
  • Konkurensi struktural — pembatalan scope secara otomatis membatalkan semua coroutine anak, mencegah kebocoran memori
  • Job dan SupervisorJob — dua mode penanganan kesalahan: pembatalan berantai (Job) dan kesalahan terisolasi (SupervisorJob)
  • GlobalScope — tidak direkomendasikan untuk produksi karena kurangnya keterikatan pada siklus hidup
  • coroutineScope vs supervisorScope — operasi paralel atom vs tugas paralel independen
  • viewModelScope dan lifecycleScope — scope siap pakai untuk Android, dibatalkan secara otomatis saat komponen berakhir
  • Fungsi pabrik — cara yang lebih disukai untuk membuat scope melalui CoroutineContext + panggilan cancel eksplisit

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.

Diskusikan proyek

Baca juga