Background Thread — en exekveringstråd som inte är kopplad till användargränssnittet, avsedd för långvariga operationer: nätverksförfrågningar, arbete med filer, JSON-tolkning, bildkomprimering, kryptering och databasfrågor. I iOS hanteras bakgrundstrådar via GCD (DispatchQueue.global) och OperationQueue, i Android — via Executors, WorkManager och Kotlin Coroutines (Dispatchers.IO, Dispatchers.Default). Enligt Apple DispatchQueue Documentation, efter att bakgrundsoperationen slutförts måste resultatet returneras till Main Thread för att uppdatera gränssnittet.
Huvudpunkter
Background Thread — vilken tråd som helst i applikationen som inte är Main Thread och inte har åtkomst till UI. Dess uppgift är att avlasta huvudtråden från tunga operationer så att gränssnittet förblir responsivt. Operativsystemet fördelar bakgrundstrådar över processorkärnorna, vilket möjliggör parallell exekvering av flera uppgifter. iOS hanterar automatiskt trådpoolen via GCD, Android — via Java Executors-pooler.
Till skillnad från Main Thread, som bearbetar händelser sekventiellt (en efter en), kan bakgrundstrådar exekveras parallellt, endast begränsade av antalet CPU-kärnor. Till exempel, på en enhet med 8 kärnor kan upp till 8 parallella bakgrundsuppgifter startas utan betydande försening. Ett överdrivet antal trådar (hundratals) leder dock till thread starvation — konkurrens om kärnor och ökning av omkostnader för kontextväxling (context switch).
Quality of Service (QoS) — en iOS-mekanism som gör det möjligt att ange prioriteten för en bakgrundsuppgift. Värden: .userInteractive (högst, nästan Main Thread), .userInitiated (användaren väntar på resultat), .default (standard), .utility (användaren väntar inte direkt), .background (lägst, för synkronisering och indexering). I Android är motsvarigheten Thread.setPriority() från 1 till 10, men Android använder också cgroups för grupphantering av trådprioriteter.
DispatchQueue.global(qos:) — det primära sättet att få en bakgrundskö i iOS. GCD (Grand Central Dispatch) skapar automatiskt en trådpool och fördelar uppgifter över kärnorna. Anropet DispatchQueue.global(qos: .background).async {} skickar ett exekveringsblock till bakgrundskön med lägsta prioritet. För uppgifter vars resultat behövs omedelbart, använd .userInitiated eller .utility.
OperationQueue — en abstraktion på högre nivå över GCD, som gör det möjligt att ange beroenden mellan operationer, maximalt antal samtidiga operationer (maxConcurrentOperationCount) och prioriteter. OperationQueue är praktisk för komplexa multitasking-kedjor: ladda ner fil -> packa upp -> spara i cache. Som standard använder OperationQueue bakgrundstrådar, om inte annat anges.
import UIKit
class ImageDownloader {
func downloadImagesSequentially() {
let urls = ["https://example.com/1.png", "https://example.com/2.png"]
// OperationQueue med 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("Laddat: \(url.lastPathComponent)")
}
}
}
}
// GCD: global bakgrundskö med olika QoS
func backgroundTaskWithQoS() {
DispatchQueue.global(qos: .userInitiated).async {
// Hög prioritet — användaren väntar på resultat
let result = self.heavyComputation()
DispatchQueue.main.async {
self.showResult(result)
}
}
}
private func heavyComputation() -> String {
Thread.sleep(forTimeInterval: 2) // simulering av arbete
return "Resultat av beräkningar"
}
private func showResult(_ result: String) {
print("Result on Main: \(result)")
}
}
I exemplet laddar OperationQueue två bilder parallellt (maxConcurrentOperationCount = 2) med bakgrund via qualityOfService = .utility. GCD-metoden backgroundTaskWithQoS använder den globala kön med .userInitiated för en uppgift vars resultat användaren förväntar sig. Båda metoderna avslutas med återgång till DispatchQueue.main för UI-uppdatering — detta är ett obligatoriskt iOS-krav.
GCD stöder två typer av köer: serial (sekventiella) och concurrent (parallella). Serial-köer utför uppgifter en efter en — detta är praktiskt för åtkomst till en delad resurs (fil, databas) utan låsningar. Concurrent-köer utför uppgifter parallellt och fördelar dem över lediga kärnor. DispatchQueue.global är alltid concurrent. För att skapa en serial-kö, använd DispatchQueue(label: "com.app.queue").
Android erbjuder flera abstraktionsnivåer för bakgrundstrådar. Det klassiska tillvägagångssättet — java.util.concurrent.Executors.newFixedThreadPool(n) eller Executors.newCachedThreadPool(). Det moderna tillvägagångssättet — Kotlin Coroutines med Dispatchers.IO (för inmatning-utmatning: nätverk, filer, databas) och Dispatchers.Default (för CPU-intensiva uppgifter: sortering, bildbehandling). WorkManager — för fördröjda och garanterade bakgrundsuppgifter.
HandlerThread — en speciell Android-klass för att skapa en bakgrundstråd med egen Looper (meddelandekö). Till skillnad från Executors, tillåter HandlerThread att skicka meddelanden och Runnable via Handler. Används för operationer som kräver köning (t.ex. sekventiell skrivning till databasen). Efter användning måste quit() eller quitSafely() anropas för att frigöra resurser.
// Android: Executors och Coroutines Dispatchers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.concurrent.Executors
class DataRepository {
private val ioExecutor = Executors.newFixedThreadPool(4)
// Klassiskt tillvägagångssätt via Executors
fun loadDataLegacy(callback: (String) -> Unit) {
ioExecutor.execute {
val result = readFromFile()
val handler = android.os.Handler(android.os.Looper.getMainLooper())
handler.post { callback(result) }
}
}
// Modernt tillvägagångssätt via Coroutines
suspend fun loadDataCoroutines(): String {
return withContext(Dispatchers.IO) {
// Filoperation — utförs i bakgrundspoolen
readFromFile()
}
// Resultatet returneras automatiskt till Dispatchers.Main
}
// CPU-intensiv uppgift på Dispatchers.Default
suspend fun processImage(pixels: IntArray): IntArray {
return withContext(Dispatchers.Default) {
// Sortering, filtrering — utförs på Default-poolen
pixels.sortedArray()
}
}
private fun readFromFile(): String {
Thread.sleep(1000) // simulering av läsning från fil
return "file_content"
}
fun cleanup() {
ioExecutor.shutdown()
}
}
Exemplet DataRepository visar utvecklingen av bakgrundstrådar i Android. Den äldre metoden loadDataLegacy använder Executors.newFixedThreadPool(4) med Handler för att återvända till Main Thread. Den moderna loadDataCoroutines använder withContext(Dispatchers.IO) — korutinen pausar under arbetet, blockerar inte tråden och återupptas automatiskt på Main Thread. Dispatchers.Default rekommenderas för CPU-bundna operationer (sortering, filtrering, datatransformering).
Kotlin Coroutines — inte bara ett sätt att arbeta med trådar, utan en fundamentalt annorlunda modell: asynkrona uppgifter är inte bundna till en specifik tråd och kan pausas (suspend) utan blockering. Detta innebär att under bakgrundsarbete upptar korutinen inte en tråd, utan frigör den för andra uppgifter. Suspension-mekanismen gör det möjligt att utföra hundratusentals samtidiga uppgifter på en pool med 4-8 trådar utan thread starvation.
Tre huvuddispatcher: Dispatchers.Main (UI, en tråd), Dispatchers.IO (som standard 64 trådar för blockerande operationer: nätverk, filer, databas), Dispatchers.Default (lika med antalet CPU-kärnor, för intensiva beräkningar). Genom att kombinera dem via withContext, växlar utvecklaren mellan trådar utan att skapa callbacks. withContext är en suspend-funktion som inte återlämnar kontrollen förrän uppgiften är slutförd.
// Korutiner: sammansättning av bakgrundsuppgifter
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
suspend fun loadUserProfile(userId: String): UserProfile =
coroutineScope {
// Parallell laddning av data från olika källor
val user = async(Dispatchers.IO) { fetchUser(userId) }
val posts = async(Dispatchers.IO) { fetchPosts(userId) }
val avatar = async(Dispatchers.Default) {
processAvatar(fetchAvatar(userId))
}
// await() — pausar tills alla uppgifter är slutförda
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 }
Funktionen loadUserProfile startar tre parallella bakgrundsuppgifter via async. fetchUser och fetchPosts är IO-bundna (nätverk), körs på Dispatchers.IO. processAvatar är CPU-bunden (bildbehandling), körs på Dispatchers.Default. await() pausar korutinen tills alla uppgifter är slutförda. Den totala exekveringstiden är lika med den maximala tiden bland de tre uppgifterna (500 ms för fetchPosts), inte summan av dem. Detta är den främsta fördelen med coroutines jämfört med sekventiell exekvering.
Structured concurrency — principen där varje korutin har ett förälderscope, och annullering av föräldern annullerar automatiskt barnkorutinerna. I Android annullerar lifecycleScope alla korutiner vid förstörelse av Activity. viewModelScope — vid rengöring av ViewModel. Detta förhindrar läckor av bakgrundsuppgifter: om användaren stängde skärmen kommer bakgrundskorutinen inte att fortsätta ladda data som ingen längre behöver.
WorkManager — ett Android Jetpack-bibliotek för att utföra bakgrundsuppgifter som måste utföras även efter omstart av enheten eller stängning av applikationen. Till skillnad från Executors och korutiner, som lever i applikationsprocessen, överlämnar WorkManager uppgiften till en systemdispatcher som garanterar exekvering under lämpliga förhållanden (nätverkstillgänglighet, batterinivå, ledigt utrymme). WorkManager är lämpligt för datasynkronisering, uppladdning av loggar, säkerhetskopiering.
En uppgift i WorkManager är en klass som ärver Worker (eller CoroutineWorker för korutiner). Worker.doWork() körs på en bakgrundstråd som tillhandahålls av WorkManager. Resultatet returneras via Result.success(), Result.retry() eller Result.failure(). Uppgifter kan kombineras i kedjor: oneTimeWorkRequest.andThen(nextRequest).enqueue(). WorkManager väljer själv den optimala exekveringstiden med hänsyn till begränsningar (Constraints).
// WorkManager med korutiner
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 {
// Körs på Dispatchers.Default (standard)
return withContext(Dispatchers.IO) {
try {
syncDataToServer()
Result.success()
} catch (e: Exception) {
if (runAttemptCount < 3) Result.retry() else Result.failure()
}
}
}
private suspend fun syncDataToServer() {
// Simulering av synkronisering
delay(1000)
}
}
// Starta WorkManager-uppgift med begränsningar
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 ärver CoroutineWorker — versionen av Worker som stöder korutiner. doWork() körs på Dispatchers.Default, växling till IO för nätverksoperationer via withContext. Constraints garanterar att synkronisering endast startar när det finns nätverk och batterinivån inte är lägre än låg. BackoffCriteria med EXPONENTIAL ökar intervallet mellan upprepade försök: 10, 20, 40 sekunder.
För regelbundna uppgifter (synkronisering var 15:e minut, skicka analys varje timme) tillhandahåller WorkManager PeriodicWorkRequestBuilder. Minsta intervall — 15 minuter. Till skillnad från OneTimeWorkRequest garanterar PeriodicWorkRequest inte exakt efterlevnad av intervallet — systemet kan kombinera flera periodiska uppgifter för att spara batteri. För exakta intervall, använd AlarmManager, men ta hänsyn till Android 12+ begränsningar för exakta alarm.
Första misstaget — att skapa en ny Thread för varje uppgift. new Thread().start() skapar en naturlig tråd med allokering av ~1 MB för stacken. För 100 parallella uppgifter är det 100 MB bara för stackar, plus omkostnader för context switch. Använd trådpooler: Executors.newFixedThreadPool(n) (Android) eller DispatchQueue.global() (iOS) — de återanvänder trådar och minskar overhead tiotals gånger.
Andra misstaget — åtkomst till muterbart tillstånd från flera bakgrundstrådar utan synkronisering. Om två bakgrundstrådar samtidigt skriver till samma ArrayList eller HashMap uppstår race condition: ConcurrentModificationException i Android, datakorruption i iOS. Lösning: använd trådsäkra samlingar (ConcurrentHashMap, CopyOnWriteArrayList) eller serialisera åtkomst via en kö (DispatchQueue serial).
Tredje misstaget — bakgrundsuppgifter utan livscykelhantering. Att starta en korutin i globalt scope utan bindning till Activitys eller ViewModels livscykel leder till läckor: uppgiften fortsätter efter att skärmen förstörts. I Android, använd lifecycleScope (Activity/Fragment) eller viewModelScope (ViewModel). I iOS — weak self i closures och annullering av uppgifter vid deinit.
Vanliga frågor
Background Thread — tråden på vilken operationer som inte är relaterade till UI utförs: nätverksförfrågningar, läsning/skrivning av filer, JSON-tolkning, beräkningar. Den avlastar Main Thread från tungt arbete och bibehåller gränssnittets responsivitet. I iOS hanteras bakgrundstrådar via GCD (DispatchQueue.global), i Android — via Executors eller Kotlin Coroutines (Dispatchers.IO, Dispatchers.Default).
Dispatchers.IO är avsett för blockerande inmatning-utmatning: läsa filer, nätverksförfrågningar, arbete med databas. Det har en pool med 64 trådar. Dispatchers.Default — för CPU-intensiva uppgifter: sortering, filtrering, bildbehandling. Dess pool är lika med antalet CPU-kärnor. Att använda Dispatchers.Default för IO-operationer kan blockera alla kärnor, och Dispatchers.IO för CPU-uppgifter kan skapa ett överdrivet antal trådar.
DispatchQueue.global(qos: .background).async { } skickar ett exekveringsblock till den globala bakgrundskön. Efter bakgrundsarbetet måste du återvända till huvudtråden via DispatchQueue.main.async { } för att uppdatera UI. För sekventiella bakgrundsuppgifter, använd OperationQueue med maxConcurrentOperationCount = 1 eller DispatchQueue(label: "serial").
Det rekommenderade antalet bakgrundstrådar är lika med antalet CPU-kärnor plus 1 för IO-bundna uppgifter. På en modern 8-kärnig enhet är det 9 trådar. Att skapa hundratals trådar leder till thread starvation: operativsystemet spenderar mer tid på kontextväxling (context switch) än på att utföra uppgifter. GCD i iOS och Executors i Android optimerar automatiskt trådpoolen för den aktuella enheten.
I Kotlin Coroutines sker återgången till Main Thread automatiskt om korutinen startades i Main-scope (lifecycleScope.launch, viewModelScope.launch). Funktionen withContext(Dispatchers.IO) pausar korutinen på IO-tråden och återupptar den automatiskt på den dispatcher där den startades (vanligtvis Main). Explicit anrop av DispatchQueue.main.async krävs inte.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också