Background Thread — thread eksekusi yang tidak terkait dengan antarmuka pengguna, ditujukan untuk operasi yang lama: permintaan jaringan, bekerja dengan file, parsing JSON, kompresi gambar, enkripsi dan permintaan basis data. Di iOS, thread latar belakang dikelola melalui GCD (DispatchQueue.global) dan OperationQueue, di Android — melalui Executors, WorkManager dan Kotlin Coroutines (Dispatchers.IO, Dispatchers.Default). Menurut Apple DispatchQueue Documentation, setelah operasi latar belakang selesai, hasilnya harus dikembalikan ke Main Thread untuk memperbarui antarmuka.
Poin utama
Background Thread — thread apa pun dalam aplikasi yang bukan Main Thread dan tidak memiliki akses ke UI. Tugasnya adalah membebaskan thread utama dari operasi berat agar antarmuka tetap responsif. Sistem operasi mendistribusikan thread latar belakang ke inti prosesor, memungkinkan beberapa tugas dijalankan secara paralel. iOS secara otomatis mengelola kumpulan thread melalui GCD, Android — melalui kumpulan Java Executors.
Tidak seperti Main Thread, yang memproses peristiwa secara berurutan (satu per satu), thread latar belakang dapat dijalankan secara paralel, hanya dibatasi oleh jumlah inti CPU. Misalnya, pada perangkat dengan 8 inti, hingga 8 tugas latar belakang paralel dapat dijalankan tanpa perlambatan signifikan. Namun, jumlah thread yang berlebihan (ratusan) menyebabkan thread starvation — persaingan untuk inti dan peningkatan biaya overhead untuk peralihan konteks (context switch).
Quality of Service (QoS) — mekanisme iOS yang memungkinkan Anda menentukan prioritas tugas latar belakang. Nilai: .userInteractive (tertinggi, hampir Main Thread), .userInitiated (pengguna menunggu hasil), .default (standar), .utility (pengguna tidak menunggu langsung), .background (terendah, untuk sinkronisasi dan pengindeksan). Di Android, analognya adalah Thread.setPriority() dari 1 hingga 10, tetapi Android juga menggunakan cgroups untuk manajemen grup prioritas thread.
DispatchQueue.global(qos:) — cara utama untuk mendapatkan antrian latar belakang di iOS. GCD (Grand Central Dispatch) secara otomatis membuat kumpulan thread dan mendistribusikan tugas ke inti. Panggilan DispatchQueue.global(qos: .background).async {} mengirim blok eksekusi ke antrian latar belakang dengan prioritas terendah. Untuk tugas yang hasilnya diperlukan segera, gunakan .userInitiated atau .utility.
OperationQueue — abstraksi tingkat lebih tinggi di atas GCD, yang memungkinkan Anda menentukan dependensi antar operasi, jumlah maksimum operasi yang dijalankan secara bersamaan (maxConcurrentOperationCount) dan prioritas. OperationQueue nyaman untuk rantai multitasking yang kompleks: unduh file -> buka -> simpan di cache. Secara default, OperationQueue menggunakan thread latar belakang, jika tidak ditentukan lain.
import UIKit
class ImageDownloader {
func downloadImagesSequentially() {
let urls = ["https://example.com/1.png", "https://example.com/2.png"]
// OperationQueue dengan maxConcurrentOperationCount = 2
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2
queue.qualityOfService = .utility
for urlString in urls {
queue.addOperation {
guard let url = URL(string: urlString),
let data = try? Data(contentsOf: url)
else { return }
DispatchQueue.main.async {
print("Dimuat: \(url.lastPathComponent)")
}
}
}
}
// GCD: antrian latar belakang global dengan QoS berbeda
func backgroundTaskWithQoS() {
DispatchQueue.global(qos: .userInitiated).async {
// Prioritas tinggi — pengguna menunggu hasil
let result = self.heavyComputation()
DispatchQueue.main.async {
self.showResult(result)
}
}
}
private func heavyComputation() -> String {
Thread.sleep(forTimeInterval: 2) // simulasi kerja
return "Hasil komputasi"
}
private func showResult(_ result: String) {
print("Result on Main: \(result)")
}
}
Dalam contoh, OperationQueue memuat dua gambar secara paralel (maxConcurrentOperationCount = 2) dengan latar belakang melalui qualityOfService = .utility. Metode GCD backgroundTaskWithQoS menggunakan antrian global dengan .userInitiated untuk tugas yang hasilnya ditunggu pengguna. Kedua pendekatan diakhiri dengan kembali ke DispatchQueue.main untuk memperbarui UI — ini adalah persyaratan wajib iOS.
GCD mendukung dua jenis antrian: serial (berurutan) dan concurrent (paralel). Antrian serial menjalankan tugas satu per satu — ini nyaman untuk akses ke sumber daya bersama (file, basis data) tanpa penguncian. Antrian concurrent menjalankan tugas secara paralel, mendistribusikannya ke inti yang bebas. DispatchQueue.global — selalu concurrent. Untuk membuat antrian serial, gunakan DispatchQueue(label: "com.app.queue").
Android menyediakan beberapa tingkat abstraksi untuk thread latar belakang. Pendekatan klasik — java.util.concurrent.Executors.newFixedThreadPool(n) atau Executors.newCachedThreadPool(). Pendekatan modern — Kotlin Coroutines dengan Dispatchers.IO (untuk input-output: jaringan, file, basis data) dan Dispatchers.Default (untuk tugas intensif CPU: pengurutan, pemrosesan gambar). WorkManager — untuk tugas latar belakang yang ditunda dan dijamin.
HandlerThread — kelas Android khusus untuk membuat thread latar belakang dengan Looper sendiri (antrian pesan). Tidak seperti Executors, HandlerThread memungkinkan pengiriman pesan dan Runnable melalui Handler. Digunakan untuk operasi yang memerlukan antrian (misalnya, penulisan berurutan ke basis data). Setelah digunakan, quit() atau quitSafely() harus dipanggil untuk membebaskan sumber daya.
// Android: Executors dan Coroutines Dispatchers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.concurrent.Executors
class DataRepository {
private val ioExecutor = Executors.newFixedThreadPool(4)
// Pendekatan klasik melalui Executors
fun loadDataLegacy(callback: (String) -> Unit) {
ioExecutor.execute {
val result = readFromFile()
val handler = android.os.Handler(android.os.Looper.getMainLooper())
handler.post { callback(result) }
}
}
// Pendekatan modern melalui Coroutines
suspend fun loadDataCoroutines(): String {
return withContext(Dispatchers.IO) {
// Operasi file — dijalankan di kumpulan latar belakang
readFromFile()
}
// Hasil secara otomatis kembali ke Dispatchers.Main
}
// Tugas intensif CPU pada Dispatchers.Default
suspend fun processImage(pixels: IntArray): IntArray {
return withContext(Dispatchers.Default) {
// Pengurutan, pemfilteran — dijalankan di kumpulan Default
pixels.sortedArray()
}
}
private fun readFromFile(): String {
Thread.sleep(1000) // simulasi membaca dari file
return "file_content"
}
fun cleanup() {
ioExecutor.shutdown()
}
}
Contoh DataRepository menunjukkan evolusi thread latar belakang di Android. Metode lama loadDataLegacy menggunakan Executors.newFixedThreadPool(4) dengan Handler untuk kembali ke Main Thread. Metode modern loadDataCoroutines menggunakan withContext(Dispatchers.IO) — coroutine menjeda selama bekerja, tidak memblokir thread, dan secara otomatis melanjutkan di Main Thread. Dispatchers.Default direkomendasikan untuk operasi CPU-bound (pengurutan, pemfilteran, transformasi data).
Kotlin Coroutines — bukan sekadar cara bekerja dengan thread, tetapi model yang secara fundamental berbeda: tugas asinkron tidak terikat pada thread tertentu dan dapat dijeda (suspend) tanpa memblokir. Ini berarti selama bekerja di latar belakang, coroutine tidak menempati thread, tetapi membebaskannya untuk tugas lain. Mekanisme suspension memungkinkan menjalankan ratusan ribu tugas concurrent pada kumpulan 4-8 thread tanpa thread starvation.
Tiga dispatcher utama: Dispatchers.Main (UI, satu thread), Dispatchers.IO (64 thread secara default untuk operasi pemblokiran: jaringan, file, basis data), Dispatchers.Default (sama dengan jumlah inti CPU, untuk komputasi intensif). Menggabungkannya melalui withContext, pengembang beralih antar thread tanpa membuat callback. withContext adalah fungsi suspend yang tidak mengembalikan kendali sampai tugas selesai.
// Coroutine: komposisi tugas latar belakang
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
suspend fun loadUserProfile(userId: String): UserProfile =
coroutineScope {
// Memuat data paralel dari berbagai sumber
val user = async(Dispatchers.IO) { fetchUser(userId) }
val posts = async(Dispatchers.IO) { fetchPosts(userId) }
val avatar = async(Dispatchers.Default) {
processAvatar(fetchAvatar(userId))
}
// await() — menjeda sampai semua tugas selesai
UserProfile(
user = user.await(),
posts = posts.await(),
avatar = avatar.await()
)
}
data class UserProfile(
val user: String,
val posts: List<String>,
val avatar: ByteArray
)
suspend fun fetchUser(id: String): String { delay(300); return "User:$id" }
suspend fun fetchPosts(id: String): List<String> { delay(500); return listOf("Post1") }
suspend fun fetchAvatar(id: String): ByteArray { delay(200); return ByteArray(1024) }
suspend fun processAvatar(data: ByteArray): ByteArray { delay(100); return data }
Fungsi loadUserProfile menjalankan tiga tugas latar belakang paralel melalui async. fetchUser dan fetchPosts — IO-bound (jaringan), dijalankan pada Dispatchers.IO. processAvatar — CPU-bound (pemrosesan gambar), dijalankan pada Dispatchers.Default. await() menjeda coroutine sampai semua tugas selesai. Waktu eksekusi total sama dengan waktu maksimum dari tiga tugas (500 ms untuk fetchPosts), bukan jumlahnya. Ini adalah keunggulan utama coroutines dibandingkan eksekusi berurutan.
Structured concurrency — prinsip di mana setiap coroutine memiliki scope induk, dan pembatalan induk secara otomatis membatalkan coroutine anak. Di Android, lifecycleScope membatalkan semua coroutine saat Activity dihancurkan. viewModelScope — saat ViewModel dibersihkan. Ini mencegah kebocoran tugas latar belakang: jika pengguna menutup layar, coroutine latar belakang tidak akan terus memuat data yang sudah tidak diperlukan siapa pun.
WorkManager — pustaka Android Jetpack untuk menjalankan tugas latar belakang yang harus dijalankan bahkan setelah perangkat dihidupkan ulang atau aplikasi ditutup. Tidak seperti Executors dan coroutine, yang hidup dalam proses aplikasi, WorkManager menyerahkan tugas ke dispatcher sistem yang menjamin eksekusi dalam kondisi yang sesuai (ketersediaan jaringan, tingkat baterai, ruang kosong). WorkManager cocok untuk sinkronisasi data, mengunggah log, pencadangan.
Tugas di WorkManager adalah kelas yang mewarisi Worker (atau CoroutineWorker untuk coroutine). Worker.doWork() dijalankan pada thread latar belakang yang disediakan oleh WorkManager. Hasil dikembalikan melalui Result.success(), Result.retry() atau Result.failure(). Tugas dapat digabungkan dalam rantai: oneTimeWorkRequest.andThen(nextRequest).enqueue(). WorkManager sendiri memilih waktu eksekusi optimal dengan mempertimbangkan batasan (Constraints).
// WorkManager dengan coroutine
import android.content.Context
import androidx.work.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class SyncWorker(
appContext: Context,
workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result {
// Dijalankan pada Dispatchers.Default (default)
return withContext(Dispatchers.IO) {
try {
syncDataToServer()
Result.success()
} catch (e: Exception) {
if (runAttemptCount < 3) Result.retry() else Result.failure()
}
}
}
private suspend fun syncDataToServer() {
// Simulasi sinkronasi
delay(1000)
}
}
// Menjalankan tugas WorkManager dengan batasan
fun scheduleSync(context: Context) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
val syncWork = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(constraints)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, java.util.concurrent.TimeUnit.SECONDS)
.build()
WorkManager.getInstance(context).enqueue(syncWork)
}
SyncWorker mewarisi CoroutineWorker — versi Worker yang mendukung coroutine. doWork() dijalankan pada Dispatchers.Default, peralihan ke IO untuk operasi jaringan melalui withContext. Constraints menjamin bahwa sinkronasi hanya akan dimulai saat ada jaringan dan tingkat baterai tidak lebih rendah dari rendah. BackoffCriteria dengan EXPONENTIAL meningkatkan interval antara percobaan ulang: 10, 20, 40 detik.
Untuk tugas reguler (sinkronasi setiap 15 menit, mengirim analitik setiap jam) WorkManager menyediakan PeriodicWorkRequestBuilder. Interval minimum — 15 menit. Tidak seperti OneTimeWorkRequest, PeriodicWorkRequest tidak menjamin kepatuhan interval yang tepat — sistem dapat menggabungkan beberapa tugas periodik untuk menghemat baterai. Untuk interval yang tepat, gunakan AlarmManager, tetapi perhatikan batasan Android 12+ untuk alarm yang tepat.
Kesalahan pertama — membuat Thread baru untuk setiap tugas. new Thread().start() membuat thread native dengan alokasi ~1 MB untuk stack. Untuk 100 tugas paralel, ini berarti 100 MB hanya untuk stack, ditambah biaya overhead untuk context switch. Gunakan kumpulan thread: Executors.newFixedThreadPool(n) (Android) atau DispatchQueue.global() (iOS) — mereka menggunakan kembali thread, mengurangi overhead puluhan kali lipat.
Kesalahan kedua — akses ke status mutable dari beberapa thread latar belakang tanpa sinkronasi. Jika dua thread latar belakang secara bersamaan menulis ke ArrayList atau HashMap yang sama, terjadilah race condition: ConcurrentModificationException di Android, kerusakan data di iOS. Solusi: gunakan koleksi thread-safe (ConcurrentHashMap, CopyOnWriteArrayList) atau serialisasi akses melalui satu antrian (DispatchQueue serial).
Kesalahan ketiga — tugas latar belakang tanpa manajemen siklus hidup. Menjalankan coroutine di scope global tanpa mengikat ke siklus hidup Activity atau ViewModel menyebabkan kebocoran: tugas terus berjalan setelah layar dihancurkan. Di Android, gunakan lifecycleScope (Activity/Fragment) atau viewModelScope (ViewModel). Di iOS — weak self di closure dan pembatalan tugas saat deinit.
Pertanyaan yang sering diajukan
Background Thread — thread tempat operasi yang tidak terkait dengan UI dijalankan: permintaan jaringan, membaca/menulis file, parsing JSON, komputasi. Ini membebaskan Main Thread dari pekerjaan berat, menjaga responsivitas antarmuka. Di iOS, thread latar belakang dikelola melalui GCD (DispatchQueue.global), di Android — melalui Executors atau Kotlin Coroutines (Dispatchers.IO, Dispatchers.Default).
Dispatchers.IO ditujukan untuk operasi input-output yang memblokir: membaca file, permintaan jaringan, bekerja dengan basis data. Ini memiliki kumpulan 64 thread. Dispatchers.Default — untuk tugas intensif CPU: pengurutan, pemfilteran, pemrosesan gambar. Kumpulannya sama dengan jumlah inti CPU. Menggunakan Dispatchers.Default untuk operasi IO dapat memblokir semua inti, dan Dispatchers.IO untuk tugas CPU — dapat membuat jumlah thread yang berlebihan.
DispatchQueue.global(qos: .background).async { } mengirim blok eksekusi ke antrian latar belakang global. Setelah pekerjaan latar belakang selesai, Anda harus kembali ke thread utama melalui DispatchQueue.main.async { } untuk memperbarui UI. Untuk tugas latar belakang berurutan, gunakan OperationQueue dengan maxConcurrentOperationCount = 1 atau DispatchQueue(label: "serial").
Jumlah yang disarankan thread latar belakang sama dengan jumlah inti CPU ditambah 1 untuk tugas IO-bound. Pada perangkat 8-inti modern, ini adalah 9 thread. Membuat ratusan thread menyebabkan thread starvation: sistem operasi menghabiskan lebih banyak waktu untuk peralihan konteks (context switch) daripada menjalankan tugas. GCD di iOS dan Executors di Android secara otomatis mengoptimalkan kumpulan thread untuk perangkat saat ini.
Di Kotlin Coroutines, kembali ke Main Thread terjadi secara otomatis jika coroutine dijalankan di Main-scope (lifecycleScope.launch, viewModelScope.launch). Fungsi withContext(Dispatchers.IO) menjeda coroutine di thread IO, dan setelah selesai secara otomatis melanjutkannya di dispatcher tempat ia dijalankan (biasanya Main). Panggilan eksplisit DispatchQueue.main.async tidak diperlukan.
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