Background Thread — isang thread ng pagpapatupad na hindi nauugnay sa user interface, na idinisenyo para sa mahabang operasyon: mga kahilingan sa network, pagtatrabaho sa mga file, JSON parsing, compression ng imahe, encryption at mga query sa database. Sa iOS, ang mga background thread ay pinamamahalaan sa pamamagitan ng GCD (DispatchQueue.global) at OperationQueue, sa Android — sa pamamagitan ng Executors, WorkManager at Kotlin Coroutines (Dispatchers.IO, Dispatchers.Default). Ayon sa Apple DispatchQueue Documentation, pagkatapos makumpleto ang background operation, ang resulta ay dapat ibalik sa Main Thread upang i-update ang interface.
Mga pangunahing punto
Background Thread — anumang thread sa application na hindi Main Thread at walang access sa UI. Ang gawain nito ay palayain ang pangunahing thread mula sa mabibigat na operasyon upang ang interface ay manatiling tumutugon. Ibinahagi ng operating system ang mga background thread sa mga core ng processor, na nagpapahintulot sa maraming mga gawain na maisagawa nang magkatulad. Awtomatikong pinamamahalaan ng iOS ang pool ng thread sa pamamagitan ng GCD, Android — sa pamamagitan ng Java Executors pool.
Hindi tulad ng Main Thread, na nagpoproseso ng mga kaganapan nang sunud-sunod (isa-isa), ang mga background thread ay maaaring isagawa nang magkatulad, limitado lamang sa bilang ng mga CPU core. Halimbawa, sa isang device na may 8 core, hanggang 8 parallel background task ang maaaring ilunsad nang walang makabuluhang pagbagal. Gayunpaman, ang sobrang dami ng mga thread (daan-daan) ay humahantong sa thread starvation — kompetisyon para sa mga core at pagtaas ng overhead para sa paglipat ng konteksto (context switch).
Quality of Service (QoS) — isang mekanismo ng iOS na nagpapahintulot sa iyo na tukuyin ang priyoridad ng isang background task. Mga halaga: .userInteractive (pinakamataas, halos Main Thread), .userInitiated (naghihintay ang user ng resulta), .default (standard), .utility (hindi direktang naghihintay ang user), .background (pinakamababa, para sa pag-sync at pag-index). Sa Android, ang katumbas ay Thread.setPriority() mula 1 hanggang 10, ngunit ang Android ay gumagamit din ng cgroups para sa group management ng mga priyoridad ng thread.
DispatchQueue.global(qos:) — ang pangunahing paraan upang makakuha ng background queue sa iOS. GCD (Grand Central Dispatch) ay awtomatikong lumilikha ng pool ng thread at namamahagi ng mga gawain sa mga core. Ang tawag na DispatchQueue.global(qos: .background).async {} ay nagpapadala ng execution block sa background queue na may pinakamababang priyoridad. Para sa mga gawain na ang resulta ay kailangan kaagad, gamitin ang .userInitiated o .utility.
OperationQueue — isang mas mataas na antas ng abstraction sa ibabaw ng GCD, na nagpapahintulot sa iyo na tukuyin ang mga dependency sa pagitan ng mga operasyon, maximum na bilang ng sabay-sabay na operasyon (maxConcurrentOperationCount) at mga priyoridad. Ang OperationQueue ay maginhawa para sa mga kumplikadong multitasking chain: i-download ang file -> i-unpack -> i-save sa cache. Sa pamamagitan ng default, ang OperationQueue ay gumagamit ng mga background thread, maliban kung iba ang tinukoy.
import UIKit
class ImageDownloader {
func downloadImagesSequentially() {
let urls = ["https://example.com/1.png", "https://example.com/2.png"]
// OperationQueue na may 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("Na-load: \(url.lastPathComponent)")
}
}
}
}
// GCD: global background queue na may iba't ibang QoS
func backgroundTaskWithQoS() {
DispatchQueue.global(qos: .userInitiated).async {
// Mataas na priyoridad — naghihintay ang user ng resulta
let result = self.heavyComputation()
DispatchQueue.main.async {
self.showResult(result)
}
}
}
private func heavyComputation() -> String {
Thread.sleep(forTimeInterval: 2) // simulasyon ng trabaho
return "Resulta ng mga pagkalkula"
}
private func showResult(_ result: String) {
print("Result on Main: \(result)")
}
}
Sa halimbawa, ang OperationQueue ay naglo-load ng dalawang larawan nang magkatulad (maxConcurrentOperationCount = 2) na may background sa pamamagitan ng qualityOfService = .utility. Ang GCD method na backgroundTaskWithQoS ay gumagamit ng global queue na may .userInitiated para sa isang gawain na ang resulta ay hinihintay ng user. Ang parehong mga diskarte ay nagtatapos sa pagbabalik sa DispatchQueue.main para i-update ang UI — ito ay isang mandatoryong kinakailangan sa iOS.
Ang GCD ay sumusuporta sa dalawang uri ng queue: serial (sunud-sunod) at concurrent (magkatulad). Ang mga serial queue ay nagsasagawa ng mga gawain nang isa-isa — ito ay maginhawa para sa pag-access sa isang shared resource (file, database) nang walang pag-lock. Ang mga concurrent queue ay nagsasagawa ng mga gawain nang magkatulad, na ipinamamahagi ang mga ito sa mga libreng core. Ang DispatchQueue.global — ay palaging concurrent. Upang lumikha ng isang serial queue, gamitin ang DispatchQueue(label: "com.app.queue").
Android ay nagbibigay ng ilang antas ng abstraction para sa mga background thread. Ang klasikong diskarte — java.util.concurrent.Executors.newFixedThreadPool(n) o Executors.newCachedThreadPool(). Ang modernong diskarte — Kotlin Coroutines na may Dispatchers.IO (para sa input-output: network, mga file, database) at Dispatchers.Default (para sa CPU-intensive na mga gawain: pag-uuri, pagproseso ng imahe). WorkManager — para sa mga delayed at garantisadong background task.
HandlerThread — isang espesyal na klase ng Android para sa paglikha ng background thread na may sariling Looper (queue ng mensahe). Hindi tulad ng Executors, pinapayagan ng HandlerThread ang pagpapadala ng mga mensahe at Runnable sa pamamagitan ng Handler. Ginagamit para sa mga operasyon na nangangailangan ng pag-queue (hal., sunud-sunod na pagsulat sa database). Pagkatapos gamitin, dapat tawagan ang quit() o quitSafely() upang palayain ang mga mapagkukunan.
// Android: Executors at Coroutines Dispatchers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.concurrent.Executors
class DataRepository {
private val ioExecutor = Executors.newFixedThreadPool(4)
// Klasikong diskarte sa pamamagitan ng Executors
fun loadDataLegacy(callback: (String) -> Unit) {
ioExecutor.execute {
val result = readFromFile()
val handler = android.os.Handler(android.os.Looper.getMainLooper())
handler.post { callback(result) }
}
}
// Modernong diskarte sa pamamagitan ng Coroutines
suspend fun loadDataCoroutines(): String {
return withContext(Dispatchers.IO) {
// Operasyon sa file — isinasagawa sa background pool
readFromFile()
}
// Ang resulta ay awtomatikong bumabalik sa Dispatchers.Main
}
// CPU-intensive na gawain sa Dispatchers.Default
suspend fun processImage(pixels: IntArray): IntArray {
return withContext(Dispatchers.Default) {
// Pag-uuri, pag-filter — isinasagawa sa Default pool
pixels.sortedArray()
}
}
private fun readFromFile(): String {
Thread.sleep(1000) // simulasyon ng pagbabasa mula sa file
return "file_content"
}
fun cleanup() {
ioExecutor.shutdown()
}
}
Ang halimbawang DataRepository ay nagpapakita ng ebolusyon ng mga background thread sa Android. Ang legacy method na loadDataLegacy ay gumagamit ng Executors.newFixedThreadPool(4) na may Handler upang bumalik sa Main Thread. Ang modernong loadDataCoroutines ay gumagamit ng withContext(Dispatchers.IO) — ang coroutine ay nagpapa-pause habang nagtatrabaho, hindi hinaharangan ang thread, at awtomatikong nagpapatuloy sa Main Thread. Ang Dispatchers.Default ay inirerekomenda para sa mga CPU-bound na operasyon (pag-uuri, pag-filter, pagbabago ng data).
Kotlin Coroutines — hindi lamang isang paraan ng pagtatrabaho sa mga thread, kundi isang pangunahing naiibang modelo: ang mga asynchronous na gawain ay hindi nakatali sa isang partikular na thread at maaaring i-suspend (suspend) nang hindi hinaharangan. Nangangahulugan ito na habang nasa background work, hindi sinasakop ng coroutine ang isang thread, ngunit pinalalaya ito para sa iba pang mga gawain. Ang mekanismo ng suspension ay nagpapahintulot sa pagpapatupad ng daan-daang libong concurrent na gawain sa isang pool ng 4-8 thread nang walang thread starvation.
Tatlong pangunahing dispatcher: Dispatchers.Main (UI, isang thread), Dispatchers.IO (64 na thread bilang default para sa mga blocking operation: network, mga file, database), Dispatchers.Default (katumbas ng bilang ng mga CPU core, para sa masinsinang pagkalkula). Sa pamamagitan ng pagsasama-sama ng mga ito sa pamamagitan ng withContext, ang developer ay lumilipat sa pagitan ng mga thread nang hindi gumagawa ng mga callback. Ang withContext ay isang suspend function na hindi nagbabalik ng kontrol hanggang sa makumpleto ang gawain.
// Coroutines: komposisyon ng mga background task
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
suspend fun loadUserProfile(userId: String): UserProfile =
coroutineScope {
// Parallel na pag-load ng data mula sa iba't ibang source
val user = async(Dispatchers.IO) { fetchUser(userId) }
val posts = async(Dispatchers.IO) { fetchPosts(userId) }
val avatar = async(Dispatchers.Default) {
processAvatar(fetchAvatar(userId))
}
// await() — nagpapa-pause hanggang makumpleto ang lahat ng gawain
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 }
Ang function na loadUserProfile ay naglulunsad ng tatlong parallel background task sa pamamagitan ng async. Ang fetchUser at fetchPosts — IO-bound (network), ay isinasagawa sa Dispatchers.IO. Ang processAvatar — CPU-bound (pagproseso ng imahe), ay isinasagawa sa Dispatchers.Default. Ang await() ay nagpapa-pause ng coroutine hanggang makumpleto ang lahat ng gawain. Ang kabuuang oras ng pagpapatupad ay katumbas ng maximum na oras sa tatlong gawain (500 ms para sa fetchPosts), hindi ang kanilang kabuuan. Ito ang pangunahing bentahe ng coroutines kumpara sa sunud-sunod na pagpapatupad.
Structured concurrency — prinsipyo kung saan ang bawat coroutine ay may parent scope, at ang pagkansela ng magulang ay awtomatikong kinakansela ang mga anak na coroutine. Sa Android, kinakansela ng lifecycleScope ang lahat ng coroutine kapag nawasak ang Activity. viewModelScope — kapag na-clear ang ViewModel. Pinipigilan nito ang pagtagas ng mga background task: kung isinara ng user ang screen, ang background coroutine ay hindi magpapatuloy sa pag-load ng data na hindi na kailangan ng sinuman.
WorkManager — library ng Android Jetpack para sa pagsasagawa ng mga background task na dapat isagawa kahit na matapos i-restart ang device o isara ang application. Hindi tulad ng Executors at coroutines, na nabubuhay sa proseso ng application, ang WorkManager ay nagpapasa ng gawain sa system dispatcher na ginagarantiyahan ang pagpapatupad sa ilalim ng angkop na mga kondisyon (availability ng network, antas ng baterya, libreng espasyo). Ang WorkManager ay angkop para sa pag-sync ng data, pag-upload ng mga log, pag-backup.
Ang isang gawain sa WorkManager ay isang klase na nagmamana ng Worker (o CoroutineWorker para sa coroutines). Ang Worker.doWork() ay isinasagawa sa isang background thread na ibinigay ng WorkManager. Ang resulta ay ibinabalik sa pamamagitan ng Result.success(), Result.retry() o Result.failure(). Ang mga gawain ay maaaring pagsamahin sa mga chain: oneTimeWorkRequest.andThen(nextRequest).enqueue(). Ang WorkManager mismo ang pumipili ng pinakamainam na oras ng pagpapatupad na isinasaalang-alang ang mga limitasyon (Constraints).
// WorkManager na may coroutines
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 {
// Isinasagawa sa 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() {
// Simulasyon ng pag-sync
delay(1000)
}
}
// Paglulunsad ng WorkManager task na may mga limitasyon
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)
}
Ang SyncWorker ay nagmamana ng CoroutineWorker — bersyon ng Worker na sumusuporta sa coroutines. Ang doWork() ay isinasagawa sa Dispatchers.Default, paglipat sa IO para sa mga network operation sa pamamagitan ng withContext. Ginagarantiyahan ng Constraints na ang pag-sync ay magsisimula lamang kapag may network at ang antas ng baterya ay hindi mas mababa sa mababa. Ang BackoffCriteria na may EXPONENTIAL ay nagpapataas ng pagitan sa pagitan ng mga paulit-ulit na pagtatangka: 10, 20, 40 segundo.
Para sa regular na gawain (pag-sync bawat 15 minuto, pagpapadala ng analytics bawat oras) ang WorkManager ay nagbibigay ng PeriodicWorkRequestBuilder. Minimum na pagitan — 15 minuto. Hindi tulad ng OneTimeWorkRequest, hindi ginagarantiyahan ng PeriodicWorkRequest ang eksaktong pagsunod sa pagitan — maaaring pagsamahin ng system ang ilang periodic na gawain upang makatipid ng baterya. Para sa eksaktong pagitan, gamitin ang AlarmManager, ngunit isaalang-alang ang mga limitasyon ng Android 12+ para sa eksaktong mga alarm.
Unang pagkakamali — paggawa ng bagong Thread para sa bawat gawain. Ang new Thread().start() ay lumilikha ng isang native thread na may alokasyon na ~1 MB para sa stack. Para sa 100 parallel na gawain, ito ay 100 MB lamang para sa mga stack, kasama ang overhead para sa context switch. Gumamit ng mga pool ng thread: Executors.newFixedThreadPool(n) (Android) o DispatchQueue.global() (iOS) — sila ay muling gumagamit ng mga thread, binabawasan ang overhead ng sampu-sampung beses.
Pangalawang pagkakamali — pag-access sa mutable state mula sa maraming background thread nang walang synchronization. Kung ang dalawang background thread ay sabay na sumusulat sa parehong ArrayList o HashMap, nangyayari ang race condition: ConcurrentModificationException sa Android, pagkasira ng data sa iOS. Solusyon: gumamit ng thread-safe na mga koleksyon (ConcurrentHashMap, CopyOnWriteArrayList) o i-serialize ang access sa pamamagitan ng isang queue (DispatchQueue serial).
Pangatlong pagkakamali — mga background task na walang pamamahala ng lifecycle. Ang paglulunsad ng coroutine sa global scope nang walang pag-binding sa lifecycle ng Activity o ViewModel ay humahantong sa pagtagas: ang gawain ay patuloy na tumatakbo pagkatapos sirain ang screen. Sa Android, gamitin ang lifecycleScope (Activity/Fragment) o viewModelScope (ViewModel). Sa iOS — weak self sa mga closure at pagkansela ng mga gawain sa deinit.
Mga madalas itanong
Background Thread — thread kung saan isinasagawa ang mga operasyon na hindi nauugnay sa UI: mga kahilingan sa network, pagbabasa/pagsulat ng mga file, JSON parsing, mga pagkalkula. Pinapalaya nito ang Main Thread mula sa mabigat na trabaho, pinapanatili ang pagtugon ng interface. Sa iOS, ang mga background thread ay pinamamahalaan sa pamamagitan ng GCD (DispatchQueue.global), sa Android — sa pamamagitan ng Executors o Kotlin Coroutines (Dispatchers.IO, Dispatchers.Default).
Dispatchers.IO ay idinisenyo para sa mga blocking input-output operation: pagbabasa ng mga file, mga kahilingan sa network, pagtatrabaho sa database. Ito ay may pool na 64 na thread. Dispatchers.Default — para sa CPU-intensive na gawain: pag-uuri, pag-filter, pagproseso ng imahe. Ang pool nito ay katumbas ng bilang ng mga CPU core. Ang paggamit ng Dispatchers.Default para sa IO operation ay maaaring harangan ang lahat ng core, at ang Dispatchers.IO para sa CPU tasks — ay maaaring lumikha ng labis na bilang ng mga thread.
DispatchQueue.global(qos: .background).async { } ay nagpapadala ng execution block sa global background queue. Pagkatapos makumpleto ang background work, kailangan mong bumalik sa pangunahing thread sa pamamagitan ng DispatchQueue.main.async { } upang i-update ang UI. Para sa sunud-sunod na background task, gamitin ang OperationQueue na may maxConcurrentOperationCount = 1 o DispatchQueue(label: "serial").
Ang inirerekomendang bilang ng background thread ay katumbas ng bilang ng mga CPU core plus 1 para sa IO-bound na gawain. Sa isang modernong 8-core device, ito ay 9 na thread. Ang paggawa ng daan-daang thread ay humahantong sa thread starvation: ang operating system ay gumugugol ng mas maraming oras sa paglipat ng konteksto (context switch) kaysa sa pagsasagawa ng mga gawain. Ang GCD sa iOS at Executors sa Android ay awtomatikong nag-o-optimize ng pool ng thread para sa kasalukuyang device.
Sa Kotlin Coroutines, ang pagbabalik sa Main Thread ay awtomatikong nangyayari kung ang coroutine ay inilunsad sa Main-scope (lifecycleScope.launch, viewModelScope.launch). Ang function na withContext(Dispatchers.IO) ay nagpapa-pause ng coroutine sa IO thread, at pagkatapos makumpleto ay awtomatikong ipagpapatuloy ito sa dispatcher kung saan ito inilunsad (karaniwan ay Main). Ang tahasang pagtawag sa DispatchQueue.main.async ay hindi kinakailangan.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din