Background Thread — istifadəçi interfeysi ilə əlaqəli olmayan, uzunmüddətli əməliyyatlar üçün nəzərdə tutulmuş icra axını: şəbəkə sorğuları, fayllarla iş, JSON pars etmə, şəkil sıxlaşdırma, şifrələmə və verilənlər bazası sorğuları. iOS-da fon axınları GCD (DispatchQueue.global) və OperationQueue vasitəsilə idarə olunur, Android-də — Executors, WorkManager və Kotlin Coroutines (Dispatchers.IO, Dispatchers.Default) ilə. Apple DispatchQueue Documentation-ya görə, fon əməliyyatı tamamlandıqdan sonra nəticə interfeysi yeniləmək üçün Main Thread-a qaytarılmalıdır.
Əsas məqamlar
Background Thread — tətbiqdə Main Thread olmayan və UI-ə girişi olmayan istənilən axın. Onun vəzifəsi əsas axını ağır əməliyyatlardan azad etməkdir ki, interfeys cavabdeh qalsın. Əməliyyat sistemi fon axınlarını prosessor nüvələri arasında bölüşdürərək bir neçə tapşırığın paralel yerinə yetirilməsinə imkan verir. iOS avtomatik olaraq GCD vasitəsilə axın hovuzunu idarə edir, Android — Java Executors hovuzları vasitəsilə.
Main Thread-dən fərqli olaraq, hadisələri ardıcıl emal edən (bir-birinin ardınca), fon axınları paralel yerinə yetirilə bilər, yalnız CPU nüvələrinin sayı ilə məhdudlaşır. Məsələn, 8 nüvəli cihazda əhəmiyyətli yavaşlama olmadan 8-ə qədər paralel fon tapşırığı işə salmaq olar. Bununla belə, həddindən artıq çox axın (yüzlərlə) thread starvation-a gətirib çıxarır — nüvələr üçün rəqabət və kontekst dəyişmə (context switch) üçün əlavə xərclərin artması.
Quality of Service (QoS) — iOS mexanizmi, fon tapşırığının prioritetini təyin etməyə imkan verir. Dəyərlər: .userInteractive (ən yüksək, demək olar Main Thread), .userInitiated (istifadəçi nəticə gözləyir), .default (standart), .utility (istifadəçi birbaşa gözləmir), .background (ən aşağı, sinxronizasiya və indeksləşdirmə üçün). Android-də analoq Thread.setPriority() 1-dən 10-a qədərdir, lakin Android həmçinin axın prioritetlərini qrup şəklində idarə etmək üçün cgroups istifadə edir.
DispatchQueue.global(qos:) — iOS-da fon növbəsi əldə etməyin əsas yolu. GCD (Grand Central Dispatch) avtomatik olaraq axın hovuzu yaradır və tapşırıqları nüvələr arasında bölüşdürür. DispatchQueue.global(qos: .background).async {} çağırışı icra blokunu ən aşağı prioritetli fon növbəsinə göndərir. Nəticəsi dərhal lazım olan tapşırıqlar üçün .userInitiated və ya .utility istifadə edin.
OperationQueue — GCD üzərində daha yüksək səviyyəli abstraksiya, əməliyyatlar arasında asılılıqları, eyni vaxtda yerinə yetirilən əməliyyatların maksimum sayını (maxConcurrentOperationCount) və prioritetləri təyin etməyə imkan verir. OperationQueue mürəkkəb çox tapşırıqlı zəncirlər üçün əlverişlidir: faylı endir -> aç -> keşdə saxla. Susmaya görə, OperationQueue başqa cür göstərilməyibsə, fon axınlarından istifadə edir.
import UIKit
class ImageDownloader {
func downloadImagesSequentially() {
let urls = ["https://example.com/1.png", "https://example.com/2.png"]
// OperationQueue maxConcurrentOperationCount = 2 ilə
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("Yükləndi: \(url.lastPathComponent)")
}
}
}
}
// GCD: müxtəlif QoS ilə qlobal fon növbəsi
func backgroundTaskWithQoS() {
DispatchQueue.global(qos: .userInitiated).async {
// Yüksək prioritet — istifadəçi nəticə gözləyir
let result = self.heavyComputation()
DispatchQueue.main.async {
self.showResult(result)
}
}
}
private func heavyComputation() -> String {
Thread.sleep(forTimeInterval: 2) // işin simulyasiyası
return "Hesablamaların nəticəsi"
}
private func showResult(_ result: String) {
print("Result on Main: \(result)")
}
}
Nümunədə OperationQueue iki şəkli paralel yükləyir (maxConcurrentOperationCount = 2) fon ilə qualityOfService = .utility vasitəsilə. GCD metodu backgroundTaskWithQoS istifadəçinin nəticəsini gözlədiyi tapşırıq üçün .userInitiated ilə qlobal növbədən istifadə edir. Hər iki yanaşma UI yeniləməsi üçün DispatchQueue.main-ə qayıtmaqla bitir — bu iOS-un məcburi tələbidir.
GCD iki növ növbə dəstəkləyir: serial (ardıcıl) və concurrent (paralel). Serial növbələr tapşırıqları bir-bir yerinə yetirir — bu, bloklamalar olmadan ümumi resursa (fayl, VerilənlərBazası) giriş üçün əlverişlidir. Concurrent növbələr tapşırıqları paralel yerinə yetirir, onları boş nüvələrə paylayır. DispatchQueue.global — həmişə concurrent. Serial növbə yaratmaq üçün DispatchQueue(label: "com.app.queue") istifadə edin.
Android fon axınları üçün bir neçə abstraksiya səviyyəsi təqdim edir. Klassik yanaşma — java.util.concurrent.Executors.newFixedThreadPool(n) və ya Executors.newCachedThreadPool(). Müasir — Kotlin Coroutines Dispatchers.IO (giriş-çıxış üçün: şəbəkə, fayllar, VerilənlərBazası) və Dispatchers.Default (CPU intensiv tapşırıqlar üçün: çeşidləmə, şəkil emalı). WorkManager — təxirə salınmış və zəmanətli fon tapşırıqları üçün.
HandlerThread — öz Looper (mesaj növbəsi) ilə fon axını yaratmaq üçün xüsusi Android sinfi. Executors-dan fərqli olaraq, HandlerThread Handler vasitəsilə mesajlar və Runnable göndərməyə imkan verir. Növbəyə qoyma tələb edən əməliyyatlar üçün istifadə olunur (məsələn, verilənlər bazasına ardıcıl yazma). İstifadədən sonra resursları boşaltmaq üçün HandlerThread-də quit() və ya quitSafely() çağırılmalıdır.
// Android: Executors və Coroutines Dispatchers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.concurrent.Executors
class DataRepository {
private val ioExecutor = Executors.newFixedThreadPool(4)
// Executors vasitəsilə klassik yanaşma
fun loadDataLegacy(callback: (String) -> Unit) {
ioExecutor.execute {
val result = readFromFile()
val handler = android.os.Handler(android.os.Looper.getMainLooper())
handler.post { callback(result) }
}
}
// Coroutines vasitəsilə müasir yanaşma
suspend fun loadDataCoroutines(): String {
return withContext(Dispatchers.IO) {
// Fayl əməliyyatı — fon hovuzunda yerinə yetirilir
readFromFile()
}
// Nəticə avtomatik olaraq Dispatchers.Main-ə qayıdır
}
// Dispatchers.Default-da CPU intensiv tapşırıq
suspend fun processImage(pixels: IntArray): IntArray {
return withContext(Dispatchers.Default) {
// Çeşidləmə, filtrləmə — Default hovuzunda yerinə yetirilir
pixels.sortedArray()
}
}
private fun readFromFile(): String {
Thread.sleep(1000) // fayldan oxuma simulyasiyası
return "file_content"
}
fun cleanup() {
ioExecutor.shutdown()
}
}
DataRepository nümunəsi Android-də fon axınlarının təkamülünü göstərir. Legacy metod loadDataLegacy Main Thread-a qayıtmaq üçün Executors.newFixedThreadPool(4) ilə Handler istifadə edir. Müasir loadDataCoroutines withContext(Dispatchers.IO) istifadə edir — korutin iş zamanı dayanır, axını bloklamır və avtomatik olaraq Main Thread-da bərpa olunur. Dispatchers.Default CPU-bound əməliyyatları üçün tövsiyə olunur (çeşidləmə, filtrləmə, məlumat transformasiyası).
Kotlin Coroutines — sadəcə axınlarla iş üsulu deyil, prinsipial olaraq fərqli model: asinxron tapşırıqlar konkret axına bağlı deyil və bloklamadan dayana bilər (suspend). Bu o deməkdir ki, fon işi zamanı korutin axını tutmur, onu digər tapşırıqlar üçün boşaldır. Suspension mexanizmi thread starvation olmadan 4-8 axın hovuzunda yüz minlərlə concurrent tapşırığı yerinə yetirməyə imkan verir.
Üç əsas dispatcher: Dispatchers.Main (UI, bir axın), Dispatchers.IO (susmaya görə 64 axın bloklayan əməliyyatlar üçün: şəbəkə, fayllar, VerilənlərBazası), Dispatchers.Default (CPU nüvələrinin sayına bərabər, intensiv hesablamalar üçün). Onları withContext vasitəsilə birləşdirərək, proqramçı callback yaratmadan axınlar arasında keçid edir. withContext — tapşırıq yerinə yetirilənə qədər idarəni qaytarmayan suspend funksiyadır.
// Korutinlər: fon tapşırıqlarının kompozisiyası
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
suspend fun loadUserProfile(userId: String): UserProfile =
coroutineScope {
// Müxtəlif mənbələrdən paralel məlumat yükləmə
val user = async(Dispatchers.IO) { fetchUser(userId) }
val posts = async(Dispatchers.IO) { fetchPosts(userId) }
val avatar = async(Dispatchers.Default) {
processAvatar(fetchAvatar(userId))
}
// await() — bütün tapşırıqlar tamamlanana qədər dayanır
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 }
loadUserProfile funksiyası async vasitəsilə üç paralel fon tapşırığı işə salır. fetchUser və fetchPosts — IO-bound (şəbəkə), Dispatchers.IO-da yerinə yetirilir. processAvatar — CPU-bound (şəkil emalı), Dispatchers.Default-da yerinə yetirilir. await() bütün tapşırıqlar tamamlanana qədər korutini dayandırır. Ümumi icra müddəti üç tapşırıq arasında maksimum vaxta bərabərdir (fetchPosts üçün 500 ms), onların cəminə deyil. Bu, coroutines-in ardıcıl icra ilə müqayisədə əsas üstünlüyüdür.
Structured concurrency — hər bir korutinin parent scope-a malik olduğu və valideynin ləğvinin avtomatik olaraq uşaq korutinləri ləğv etdiyi prinsip. Android-də lifecycleScope Activity məhv olduqda bütün korutinləri ləğv edir. viewModelScope — ViewModel təmizləndikdə. Bu, fon tapşırıqlarının sızmasının qarşısını alır: istifadəçi ekranı bağladısa, fon korutini artıq heç kimə lazım olmayan məlumatları yükləməyə davam etməyəcək.
WorkManager — cihaz yenidən başladıldıqdan və ya tətbiq bağlandıqdan sonra belə yerinə yetirilməli olan fon tapşırıqları üçün Android Jetpack kitabxanası. Tətbiq prosesində yaşayan Executors və korutinlərdən fərqli olaraq, WorkManager tapşırığı sistem dispatcherinə ötürür, o da uyğun şərtlər daxilində (şəbəkə mövcudluğu, batareya səviyyəsi, boş yer) icranı təmin edir. WorkManager məlumat sinxronizasiyası, logların yüklənməsi, ehtiyat nüsxələrin yaradılması üçün uyğundur.
WorkManager-də tapşırıq Worker (və ya korutinlər üçün CoroutineWorker) sinfindən miras alan sinifdir. Worker.doWork() WorkManager tərəfindən təmin edilən fon axınında yerinə yetirilir. Nəticə Result.success(), Result.retry() və ya Result.failure() vasitəsilə qaytarılır. Tapşırıqlar zəncirlərə birləşdirilə bilər: oneTimeWorkRequest.andThen(nextRequest).enqueue(). WorkManager məhdudiyyətləri (Constraints) nəzərə alaraq optimal icra vaxtını özü seçir.
// WorkManager korutinlərlə
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 {
// Dispatchers.Default-da yerinə yetirilir (susmaya görə)
return withContext(Dispatchers.IO) {
try {
syncDataToServer()
Result.success()
} catch (e: Exception) {
if (runAttemptCount < 3) Result.retry() else Result.failure()
}
}
}
private suspend fun syncDataToServer() {
// Sinxronizasiya simulyasiyası
delay(1000)
}
}
// Məhdudiyyətlərlə WorkManager tapşırığının işə salınması
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 CoroutineWorker — korutinləri dəstəkləyən Worker versiyasından miras alır. doWork() Dispatchers.Default-da yerinə yetirilir, şəbəkə əməliyyatları üçün IO-ya keçid withContext vasitəsilə olur. Constraints sinxronizasiyanın yalnız şəbəkə mövcud olduqda və batareya səviyyəsi aşağıdan aşağı olmadıqda işə düşəcəyini təmin edir. EXPONENTIAL ilə BackoffCriteria təkrar cəhdlər arasında intervalı artırır: 10, 20, 40 saniyə.
Müntəzəm tapşırıqlar üçün (hər 15 dəqiqədən bir sinxronizasiya, saatda bir dəfə analitika göndərmə) WorkManager PeriodicWorkRequestBuilder təqdim edir. Minimal interval — 15 dəqiqə. OneTimeWorkRequest-dən fərqli olaraq, PeriodicWorkRequest intervala dəqiq riayət olunmasına zəmanət vermir — sistem batareyaya qənaət etmək üçün bir neçə dövri tapşırığı birləşdirə bilər. Dəqiq intervallar üçün AlarmManager istifadə edin, lakin Android 12+ dəqiq alarm məhdudiyyətlərini nəzərə alın.
Birinci səhv — hər tapşırıq üçün yeni Thread yaratmaq. new Thread().start() stek üçün ~1 MB ayırma ilə native axın yaradır. 100 paralel tapşırıq üçün bu, yalnız steklər üçün 100 MB, üstəlik context switch üçün əlavə xərclər deməkdir. Axın hovuzlarından istifadə edin: Executors.newFixedThreadPool(n) (Android) və ya DispatchQueue.global() (iOS) — onlar axınları təkrar istifadə edir, overhead-i dəfələrlə azaldır.
İkinci səhv — sinxronizasiya olmadan bir neçə fon axınından mutable vəziyyətə giriş. İki fon axını eyni anda eyni ArrayList və ya HashMap-ə yazırsa, race condition yaranır: Android-də ConcurrentModificationException, iOS-da data corruption. Həll: thread-safe kolleksiyalardan (ConcurrentHashMap, CopyOnWriteArrayList) istifadə edin və ya girişi bir növbə (DispatchQueue serial) vasitəsilə serializə edin.
Üçüncü səhv — həyat dövrü idarəsi olmadan fon tapşırıqları. Korutinin Activity və ya ViewModel-in həyat dövrünə bağlanmadan global scope-da işə salınması sızmalara gətirib çıxarır: tapşırıq ekran məhv edildikdən sonra da davam edir. Android-də lifecycleScope (Activity/Fragment) və ya viewModelScope (ViewModel) istifadə edin. iOS-da — closure-larda weak self və deinit zamanı tapşırıqların ləğvi.
Tez-tez verilən suallar
Background Thread — UI ilə əlaqəli olmayan əməliyyatların yerinə yetirildiyi axın: şəbəkə sorğuları, faylların oxunması/yazılması, JSON pars etmə, hesablamalar. O, Main Thread-i ağır işdən azad edərək interfeysin cavabdehliyini qoruyur. iOS-da fon axınları GCD (DispatchQueue.global) vasitəsilə, Android-də — Executors və ya Kotlin Coroutines (Dispatchers.IO, Dispatchers.Default) ilə idarə olunur.
Dispatchers.IO bloklayan giriş-çıxış əməliyyatları üçün nəzərdə tutulub: faylların oxunması, şəbəkə sorğuları, verilənlər bazası ilə iş. Onun 64 axın hovuzu var. Dispatchers.Default — CPU intensiv tapşırıqlar üçün: çeşidləmə, filtrləmə, şəkil emalı. Onun hovuzu CPU nüvələrinin sayına bərabərdir. Dispatchers.Default-un IO əməliyyatları üçün istifadəsi bütün nüvələri bloklaya bilər, Dispatchers.IO-nun CPU tapşırıqları üçün istifadəsi isə həddindən artıq çox axın yarada bilər.
DispatchQueue.global(qos: .background).async { } icra blokunu qlobal fon növbəsinə göndərir. Fon işi bitdikdən sonra UI yeniləməsi üçün DispatchQueue.main.async { } vasitəsilə əsas axına qayıtmaq lazımdır. Ardıcıl fon tapşırıqları üçün OperationQueue-dən maxConcurrentOperationCount = 1 ilə və ya DispatchQueue(label: "serial") istifadə edin.
Tövsiyə olunan fon axınlarının sayı CPU nüvələrinin sayı üstəgəl 1 IO-bound tapşırıqları üçündür. Müasir 8 nüvəli cihazda bu 9 axındır. Yüzlərlə axının yaradılması thread starvation-a gətirib çıxarır: OS tapşırıqların yerinə yetirilməsindən daha çox vaxtını kontekst dəyişməyə (context switch) sərf edir. iOS-da GCD və Android-də Executors avtomatik olaraq axın hovuzunu cari cihaza uyğun optimallaşdırır.
Kotlin Coroutines-də Main Thread-a qayıdış avtomatik baş verir, əgər korutin Main-scope-da işə salınıbsa (lifecycleScope.launch, viewModelScope.launch). withContext(Dispatchers.IO) funksiyası korutini IO axınında dayandırır və tamamlandıqdan sonra avtomatik olaraq onu işə salındığı dispatcher-də (adətən Main) bərpa edir. DispatchQueue.main.async-in açıq çağırışı tələb olunmur.
Nəticə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun