Background Thread — kullanıcı arayüzüyle ilişkili olmayan, uzun süreli işlemler (ağ istekleri, dosya işlemleri, JSON ayrıştırma, görüntü sıkıştırma, şifreleme ve veritabanı sorguları) için tasarlanmış bir yürütme iş parçacığıdır. iOS'ta arka plan iş parçacıkları GCD (DispatchQueue.global) ve OperationQueue aracılığıyla yönetilir; Android'de ise Executors, WorkManager ve Kotlin Coroutines (Dispatchers.IO, Dispatchers.Default) aracılığıyla yönetilir. Apple DispatchQueue Dokümantasyonu'na göre, arka plan işlemi tamamlandıktan sonra arayüzü güncellemek için sonucun Main Thread'e döndürülmesi gerekir.
Anahtar Noktalar
Background Thread — uygulamada Main Thread olmayan ve UI'ya erişimi olmayan herhangi bir iş parçacığıdır. Görevi, arayüzün duyarlı kalması için ana iş parçacığını ağır işlemlerden kurtarmaktır. İşletim sistemi, arka plan iş parçacıklarını CPU çekirdeklerine dağıtarak birden çok görevin paralel olarak yürütülmesine olanak tanır. iOS, iş parçacığı havuzunu GCD aracılığıyla, Android ise Java Executors havuzları aracılığıyla otomatik olarak yönetir.
Olayları sırayla (birbiri ardına) işleyen Main Thread'un aksine, arka plan iş parçacıkları yalnızca CPU çekirdeği sayısıyla sınırlı olarak paralel olarak çalışabilir. Örneğin, 8 çekirdekli bir cihazda önemli bir yavaşlama olmadan 8'e kadar paralel arka plan görevi çalıştırılabilir. Ancak aşırı sayıda iş parçacığı (yüzlerce), thread starvation — çekirdekler için rekabet ve bağlam değiştirme (context switch) yükünün artmasına yol açar.
Quality of Service (QoS) — bir arka plan görevinin önceliğini belirtmeye olanak tanıyan iOS mekanizmasıdır. Değerler: .userInteractive (en yüksek, neredeyse Main Thread), .userInitiated (kullanıcı sonucu bekliyor), .default (standart), .utility (kullanıcı doğrudan beklemiyor), .background (en düşük, senkronizasyon ve indeksleme için). Android'de eşdeğeri, 1'den 10'a kadar Thread.setPriority()'dir, ancak Android ayrıca iş parçacığı önceliklerinin grup yönetimi için cgroups kullanır.
DispatchQueue.global(qos:) — iOS'ta bir arka plan kuyruğu elde etmenin birincil yoludur. GCD (Grand Central Dispatch) otomatik olarak bir iş parçacığı havuzu oluşturur ve görevleri çekirdeklere dağıtır. DispatchQueue.global(qos: .background).async {} çağrısı, en düşük öncelikli arka plan kuyruğuna bir blok gönderir. Sonucu hemen gereken görevler için .userInitiated veya .utility kullanın.
OperationQueue — GCD üzerinde işlemler arasında bağımlılıklar, maksimum eşzamanlı işlem sayısı (maxConcurrentOperationCount) ve öncelikler ayarlamaya olanak tanıyan daha yüksek seviyeli bir soyutlamadır. OperationQueue, karmaşık çoklu görev zincirleri (dosya indir → çıkart → önbelleğe kaydet) için kullanışlıdır. Varsayılan olarak, aksi belirtilmediği sürece OperationQueue arka plan iş parçacıklarını kullanır.
import UIKit
class ImageDownloader {
func downloadImagesSequentially() {
let urls = ["https://example.com/1.png", "https://example.com/2.png"]
// maxConcurrentOperationCount = 2 ile OperationQueue
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üklendi: \(url.lastPathComponent)")
}
}
}
}
// GCD: farklı QoS ile küresel arka plan kuyruğu
func backgroundTaskWithQoS() {
DispatchQueue.global(qos: .userInitiated).async {
// Yüksek öncelik — kullanıcı sonucu bekliyor
let result = self.heavyComputation()
DispatchQueue.main.async {
self.showResult(result)
}
}
}
private func heavyComputation() -> String {
Thread.sleep(forTimeInterval: 2) // iş simülasyonu
return "Hesaplama sonucu"
}
private func showResult(_ result: String) {
print("Result on Main: \(result)")
}
}
Örnekte, OperationQueue qualityOfService = .utility aracılığıyla arka plan QoS ile iki görüntüyü paralel olarak yükler (maxConcurrentOperationCount = 2). GCD yöntemi backgroundTaskWithQoS, kullanıcının sonucunu beklediği bir görev için .userInitiated ile küresel bir kuyruk kullanır. Her iki yaklaşım da UI'yı güncellemek için DispatchQueue.main'e dönerek sonlanır — bu iOS'ta zorunlu bir gerekliliktir.
GCD iki tür kuyruğu destekler: seri (serial) ve eşzamanlı (concurrent). Seri kuyruklar görevleri birbiri ardına yürütür — bu, kilit olmadan paylaşılan bir kaynağa (dosya, DB) erişmek için kullanışlıdır. Eşzamanlı kuyruklar görevleri paralel olarak yürütür ve bunları mevcut çekirdeklere dağıtır. DispatchQueue.global her zaman eşzamanlıdır. Seri bir kuyruk oluşturmak için DispatchQueue(label: "com.app.queue") kullanın.
Android, arka plan iş parçacıkları için birden çok soyutlama düzeyi sağlar. Klasik yaklaşım java.util.concurrent.Executors.newFixedThreadPool(n) veya Executors.newCachedThreadPool()'dur. Modern yaklaşım, Dispatchers.IO (G/Ç için: ağ, dosyalar, DB) ve Dispatchers.Default (CPU yoğun görevler için: sıralama, görüntü işleme) ile Kotlin Coroutines'dir. WorkManager, ertelenmiş ve garantili arka plan görevleri içindir.
HandlerThread — kendi Looper'ına (mesaj kuyruğu) sahip bir arka plan iş parçacığı oluşturmak için özel bir Android sınıfıdır. Executors'tan farklı olarak HandlerThread, bir Handler aracılığıyla mesaj ve Runnable göndermeye olanak tanır. Kuyruğa alınması gereken işlemler (örneğin, sıralı DB yazma) için kullanılır. Kullanımdan sonra, kaynakları serbest bırakmak için quit() veya quitSafely() çağrılmalıdır.
// Android: Executors ve Coroutines Dağıtıcıları
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.concurrent.Executors
class DataRepository {
private val ioExecutor = Executors.newFixedThreadPool(4)
// Executors aracılığıyla klasik yaklaşım
fun loadDataLegacy(callback: (String) -> Unit) {
ioExecutor.execute {
val result = readFromFile()
val handler = android.os.Handler(android.os.Looper.getMainLooper())
handler.post { callback(result) }
}
}
// Coroutines aracılığıyla modern yaklaşım
suspend fun loadDataCoroutines(): String {
return withContext(Dispatchers.IO) {
// Dosya işlemi — arka plan havuzunda yürütülüyor
readFromFile()
}
// Sonuç otomatik olarak Dispatchers.Main'e döner
}
// Dispatchers.Default'ta CPU yoğun görev
suspend fun processImage(pixels: IntArray): IntArray {
return withContext(Dispatchers.Default) {
// Sıralama, filtreleme — Default havuzunda yürütülüyor
pixels.sortedArray()
}
}
private fun readFromFile(): String {
Thread.sleep(1000) // dosyadan okuma simülasyonu
return "file_content"
}
fun cleanup() {
ioExecutor.shutdown()
}
}
DataRepository örneği, Android'te arka plan iş parçacıklarının evrimini gösterir. Eski yöntem loadDataLegacy, Main Thread'e dönmek için bir Handler ile Executors.newFixedThreadPool(4) kullanır. Modern loadDataCoroutines, withContext(Dispatchers.IO) kullanır — coroutine, iş parçacığını bloke etmeden yürütme sırasında askıya alınır ve otomatik olarak Main Thread'de devam eder. Dispatchers.Default, CPU'ya bağlı işlemler (sıralama, filtreleme, veri dönüştürme) için önerilir.
Kotlin Coroutines — yalnızca iş parçacıklarıyla çalışmanın bir yolu değil, temelde farklı bir modeldir: asenkron görevler belirli bir iş parçacığına bağlı değildir ve bloke etmeden askıya alınabilir. Bu, arka plandayken bir coroutine'in bir iş parçacığını işgal etmediği, diğer görevler için serbest bıraktığı anlamına gelir. Askıya alma mekanizması, thread starvation olmadan 4–8 iş parçacıklı bir havuzda yüz binlerce eşzamanlı görevin yürütülmesine olanak tanır.
Üç ana dağıtıcı: Dispatchers.Main (UI, bir iş parçacığı), Dispatchers.IO (varsayılan olarak bloke edici işlemler için 64 iş parçacığı: ağ, dosyalar, DB), Dispatchers.Default (CPU çekirdeği sayısına eşit, yoğun hesaplamalar için). Bunları withContext aracılığıyla birleştirerek geliştirici, callback oluşturmadan iş parçacıkları arasında geçiş yapar. withContext, görev tamamlanana kadar kontrolü geri vermeyen bir suspend işlevidir.
// Coroutines: arka plan görevlerinin bileşimi
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
suspend fun loadUserProfile(userId: String): UserProfile =
coroutineScope {
// Farklı kaynaklardan paralel veri yükleme
val user = async(Dispatchers.IO) { fetchUser(userId) }
val posts = async(Dispatchers.IO) { fetchPosts(userId) }
val avatar = async(Dispatchers.Default) {
processAvatar(fetchAvatar(userId))
}
// await() — tüm görevler tamamlanana kadar askıda kalı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 işlevi, async aracılığıyla üç paralel arka plan görevi başlatır. fetchUser ve fetchPosts IO'ya bağlıdır (ağ), Dispatchers.IO'da yürütülür. processAvatar CPU'ya bağlıdır (görüntü işleme), Dispatchers.Default'da yürütülür. await(), tüm görevler tamamlanana kadar coroutine'i askıya alır. Toplam yürütme süresi, üç görevin toplamına değil, maksimum süresine (fetchPosts için 500 ms) eşittir. Bu, sıralı yürütmeye kıyasla coroutines'in önemli bir avantajıdır.
Structured concurrency — her coroutine'in bir üst kapsama sahip olduğu ve üstün iptal edilmesinin alt coroutine'leri otomatik olarak iptal ettiği ilkedir. Android'de lifecycleScope, Activity yok edildiğinde tüm coroutine'leri iptal eder. viewModelScope, ViewModel temizlendiğinde bunu yapar. Bu, arka plan görev sızıntılarını önler: kullanıcı ekranı kapatırsa, arka plandayken coroutine artık ihtiyaç duyulmayan verileri yüklemeye devam etmez.
WorkManager — cihaz yeniden başlatıldıktan veya uygulama kapatıldıktan sonra bile tamamlanması gereken arka plan görevlerini yürütmek için bir Android Jetpack kitaplığıdır. Uygulama süreci içinde yaşayan Executors ve coroutine'lerin aksine WorkManager, görevi uygun koşullar altında (ağ kullanılabilirliği, pil şarjı, boş alan) yürütmeyi garanti eden bir sistem dağıtıcısına teslim eder. WorkManager, veri senkronizasyonu, günlük yükleme ve yedekleme için uygundur.
WorkManager'daki bir görev, Worker'ı (veya coroutine'ler için CoroutineWorker) genişleten bir sınıftır. Worker.doWork(), WorkManager tarafından sağlanan bir arka plan iş parçacığında yürütülür. Sonuç, Result.success(), Result.retry() veya Result.failure() aracılığıyla döndürülür. Görevler zincirlenebilir: oneTimeWorkRequest.andThen(nextRequest).enqueue(). WorkManager, kısıtlamaları (Constraints) dikkate alarak en uygun yürütme zamanını kendisi seçer.
// Coroutines ile WorkManager
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'ta yürütülüyor (varsayılan)
return withContext(Dispatchers.IO) {
try {
syncDataToServer()
Result.success()
} catch (e: Exception) {
if (runAttemptCount < 3) Result.retry() else Result.failure()
}
}
}
private suspend fun syncDataToServer() {
// Senkronizasyon simülasyonu
delay(1000)
}
}
// Kısıtlamalarla WorkManager görevi başlatma
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, coroutine'leri destekleyen Worker sürümü olan CoroutineWorker'ı genişletir. doWork(), Dispatchers.Default'da yürütülür ve withContext aracılığıyla ağ işlemleri için IO'ya geçer. Constraints, senkronizasyonun yalnızca ağ kullanılabilir olduğunda ve pil şarjı düşük seviyenin altında olmadığında başlamasını sağlar. EXPONENTIAL ile BackoffCriteria, yeniden deneme aralığını artırır: 10, 20, 40 saniye.
Düzenli görevler (her 15 dakikada bir senkronizasyon, saatte bir analitik gönderme) için WorkManager, PeriodicWorkRequestBuilder sağlar. Minimum aralık 15 dakikadır. OneTimeWorkRequest'in aksine, PeriodicWorkRequest tam aralığa uyulmasını garanti etmez — sistem, pilden tasarruf etmek için birden çok periyodik görevi gruplayabilir. Tam aralıklar için AlarmManager kullanın, ancak Android 12+'nın tam alarmlar üzerindeki kısıtlamalarını göz önünde bulundurun.
İlk hata — her görev için yeni bir Thread oluşturmak. new Thread().start(), yığın için yaklaşık 1 MB ayıran yerel bir iş parçacığı oluşturur. 100 paralel görev için bu yalnızca yığınlar için 100 MB'dir, ayrıca bağlam değiştirme yükü de vardır. İş parçacığı havuzları kullanın: Executors.newFixedThreadPool(n) (Android) veya DispatchQueue.global() (iOS) — iş parçacıklarını yeniden kullanarak yükü katbekat azaltır.
İkinci hata — senkronizasyon olmadan birden çok arka plan iş parçacığından değişebilir duruma erişmek. İki arka plan iş parçacığı aynı anda aynı ArrayList veya HashMap'e yazarsa, yarış koşulları oluşur: Android'de ConcurrentModificationException, iOS'ta veri bozulması. Çözüm: iş parçacığı güvenli koleksiyonlar (ConcurrentHashMap, CopyOnWriteArrayList) kullanın veya tek bir kuyruk (DispatchQueue serial) aracılığıyla erişimi sıralayın.
Üçüncü hata — yaşam döngüsü yönetimi olmayan arka plan görevleri. Bir coroutine'i Activity veya ViewModel yaşam döngüsüne bağlamadan küresel kapsamda başlatmak sızıntılara yol açar: görev, ekran yok edildikten sonra da çalışmaya devam eder. Android'de lifecycleScope (Activity/Fragment) veya viewModelScope (ViewModel) kullanın. iOS'ta closure'larda weak self kullanın ve deinit'te görevleri iptal edin.
Sıkça Sorulan Sorular
Background Thread — UI ile ilgili olmayan işlemlerin (ağ istekleri, dosya okuma/yazma, JSON ayrıştırma, hesaplamalar) yürütüldüğü bir iş parçacığıdır. Main Thread'i ağır işlerden kurtararak arayüzün duyarlı kalmasını sağlar. iOS'ta arka plan iş parçacıkları GCD (DispatchQueue.global) aracılığıyla; Android'de Executors veya Kotlin Coroutines (Dispatchers.IO, Dispatchers.Default) aracılığıyla yönetilir.
Dispatchers.IO bloke edici G/Ç işlemleri için tasarlanmıştır: dosya okuma, ağ istekleri, DB ile çalışma. 64 iş parçacıklı bir havuza sahiptir. Dispatchers.Default CPU yoğun görevler içindir: sıralama, filtreleme, görüntü işleme. Havuzu CPU çekirdeği sayısına eşittir. G/Ç işlemleri için Dispatchers.Default kullanmak tüm çekirdekleri bloke edebilir ve CPU görevleri için Dispatchers.IO kullanmak aşırı sayıda iş parçacığı oluşturabilir.
DispatchQueue.global(qos: .background).async { } küresel arka plan kuyruğuna bir blok gönderir. Arka plan çalışması tamamlandıktan sonra, UI'yı güncellemek için DispatchQueue.main.async { } aracılığıyla ana iş parçacığına dönmek gerekir. Sıralı arka plan görevleri için maxConcurrentOperationCount = 1 ile OperationQueue veya DispatchQueue(label: "serial") kullanın.
Önerilen arka plan iş parçacığı sayısı, IO'ya bağlı görevler için CPU çekirdeği sayısı artı 1'e eşittir. Modern 8 çekirdekli bir cihazda bu 9 iş parçacığıdır. Yüzlerce iş parçacığı oluşturmak thread starvation'a yol açar: işletim sistemi, görevleri yürütmekten daha fazla zamanını bağlam değiştirmeye harcar. iOS'ta GCD ve Android'de Executors, iş parçacığı havuzunu mevcut cihaz için otomatik olarak optimize eder.
Kotlin Coroutines'te, coroutine bir Main kapsamında (lifecycleScope.launch, viewModelScope.launch) başlatıldıysa Main Thread'e dönüş otomatik olarak gerçekleşir. withContext(Dispatchers.IO) işlevi, coroutine'i bir IO iş parçacığında askıya alır ve tamamlandıktan sonra başlatıldığı dağıtıcıda (genellikle Main) otomatik olarak devam ettirir. DispatchQueue.main.async'in açık bir şekilde çağrılması gerekmez.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun