Background Thread — vlákno provádění, které není spojeno s uživatelským rozhraním, určené pro dlouhodobé operace: síťové požadavky, práce se soubory, zpracování JSON, komprese obrázků, šifrování a dotazy do databáze. V iOS jsou vlákna na pozadí spravována pomocí GCD (DispatchQueue.global) a OperationQueue, v Androidu — pomocí Executors, WorkManager a Kotlin Coroutines (Dispatchers.IO, Dispatchers.Default). Podle Apple DispatchQueue Documentation, po dokončení operace na pozadí musí být výsledek vrácen do Main Thread pro aktualizaci rozhraní.
Hlavní body
Background Thread — jakékoli vlákno v aplikaci, které není Main Thread a nemá přístup k UI. Jeho úkolem je uvolnit hlavní vlákno od těžkých operací, aby rozhraní zůstalo responzivní. Operační systém rozděluje vlákna na pozadí mezi jádra procesoru, což umožňuje paralelní provádění více úloh. iOS automaticky spravuje fond vláken prostřednictvím GCD, Android — prostřednictvím fondů Java Executors.
Na rozdíl od Main Thread, který zpracovává události sekvenčně (jednu po druhé), vlákna na pozadí mohou být prováděna paralelně, omezená pouze počtem jader CPU. Například na zařízení s 8 jádry lze spustit až 8 paralelních úloh na pozadí bez výrazného zpomalení. Nadměrný počet vláken (stovky) však vede k thread starvation — konkurenci o jádra a růstu režijních nákladů na přepínání kontextu (context switch).
Quality of Service (QoS) — mechanismus iOS, který umožňuje nastavit prioritu úlohy na pozadí. Hodnoty: .userInteractive (nejvyšší, téměř Main Thread), .userInitiated (uživatel očekává výsledek), .default (standardní), .utility (uživatel nečeká přímo), .background (nejnižší, pro synchronizaci a indexování). V Androidu je analogem Thread.setPriority() od 1 do 10, ale Android také používá cgroups pro skupinovou správu priorit vláken.
DispatchQueue.global(qos:) — hlavní způsob získání fronty na pozadí v iOS. GCD (Grand Central Dispatch) automaticky vytváří fond vláken a rozděluje úlohy mezi jádra. Volání DispatchQueue.global(qos: .background).async {} odešle blok provádění do fronty na pozadí s nejnižší prioritou. Pro úlohy, jejichž výsledek je potřeba okamžitě, použijte .userInitiated nebo .utility.
OperationQueue — abstrakce vyšší úrovně nad GCD, která umožňuje nastavit závislosti mezi operacemi, maximální počet současně prováděných operací (maxConcurrentOperationCount) a priority. OperationQueue je vhodná pro složité multitaskingové řetězce: stáhnout soubor -> rozbalit -> uložit do mezipaměti. Ve výchozím nastavení OperationQueue používá vlákna na pozadí, pokud není uvedeno jinak.
import UIKit
class ImageDownloader {
func downloadImagesSequentially() {
let urls = ["https://example.com/1.png", "https://example.com/2.png"]
// OperationQueue s 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čteno: \(url.lastPathComponent)")
}
}
}
}
// GCD: globální fronta na pozadí s různými QoS
func backgroundTaskWithQoS() {
DispatchQueue.global(qos: .userInitiated).async {
// Vysoká priorita — uživatel čeká na výsledek
let result = self.heavyComputation()
DispatchQueue.main.async {
self.showResult(result)
}
}
}
private func heavyComputation() -> String {
Thread.sleep(forTimeInterval: 2) // simulace práce
return "Výsledek výpočtů"
}
private func showResult(_ result: String) {
print("Result on Main: \(result)")
}
}
V příkladu OperationQueue načítá dva obrázky paralelně (maxConcurrentOperationCount = 2) s pozadím prostřednictvím qualityOfService = .utility. Metoda GCD backgroundTaskWithQoS používá globální frontu s .userInitiated pro úlohu, jejíž výsledek uživatel očekává. Oba přístupy končí návratem na DispatchQueue.main pro aktualizaci UI — to je povinný požadavek iOS.
GCD podporuje dva typy front: serial (sekvenční) a concurrent (paralelní). Serial fronty provádějí úlohy jednu po druhé — to je vhodné pro přístup ke sdílenému zdroji (soubor, databáze) bez zámků. Concurrent fronty provádějí úlohy paralelně a rozdělují je na volná jádra. DispatchQueue.global — je vždy concurrent. Pro vytvoření serial fronty použijte DispatchQueue(label: "com.app.queue").
Android poskytuje několik úrovní abstrakce pro vlákna na pozadí. Klasický přístup — java.util.concurrent.Executors.newFixedThreadPool(n) nebo Executors.newCachedThreadPool(). Moderní přístup — Kotlin Coroutines s Dispatchers.IO (pro vstup-výstup: síť, soubory, databáze) a Dispatchers.Default (pro úlohy náročné na CPU: třídění, zpracování obrázků). WorkManager — pro odložené a garantované úlohy na pozadí.
HandlerThread — speciální třída Androidu pro vytvoření vlákna na pozadí s vlastním Looperem (frontou zpráv). Na rozdíl od Executors, HandlerThread umožňuje odesílat zprávy a Runnable prostřednictvím Handleru. Používá se pro operace vyžadující zařazení do fronty (např. sekvenční zápis do databáze). Po použití je třeba zavolat quit() nebo quitSafely() pro uvolnění prostředků.
// Android: Executors a Coroutines Dispatchers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.concurrent.Executors
class DataRepository {
private val ioExecutor = Executors.newFixedThreadPool(4)
// Klasický přístup přes Executors
fun loadDataLegacy(callback: (String) -> Unit) {
ioExecutor.execute {
val result = readFromFile()
val handler = android.os.Handler(android.os.Looper.getMainLooper())
handler.post { callback(result) }
}
}
// Moderní přístup přes Coroutines
suspend fun loadDataCoroutines(): String {
return withContext(Dispatchers.IO) {
// Operace se souborem — provádí se ve fondu na pozadí
readFromFile()
}
// Výsledek se automaticky vrací na Dispatchers.Main
}
// Úloha náročná na CPU na Dispatchers.Default
suspend fun processImage(pixels: IntArray): IntArray {
return withContext(Dispatchers.Default) {
// Třídění, filtrování — provádí se na Default fondu
pixels.sortedArray()
}
}
private fun readFromFile(): String {
Thread.sleep(1000) // simulace čtení ze souboru
return "file_content"
}
fun cleanup() {
ioExecutor.shutdown()
}
}
Příklad DataRepository ukazuje evoluci vláken na pozadí v Androidu. Starší metoda loadDataLegacy používá Executors.newFixedThreadPool(4) s Handlerem pro návrat na Main Thread. Moderní loadDataCoroutines používá withContext(Dispatchers.IO) — korutina se pozastaví během práce, neblokuje vlákno a automaticky pokračuje na Main Thread. Dispatchers.Default se doporučuje pro operace CPU-bound (třídění, filtrování, transformace dat).
Kotlin Coroutines — není jen způsob práce s vlákny, ale zásadně odlišný model: asynchronní úlohy nejsou vázány na konkrétní vlákno a mohou být pozastaveny (suspend) bez blokování. To znamená, že během práce na pozadí korutina neobsazuje vlákno, ale uvolňuje jej pro jiné úlohy. Mechanismus suspension umožňuje provádět stovky tisíc souběžných úloh na fondu 4-8 vláken bez thread starvation.
Tři hlavní dispatchery: Dispatchers.Main (UI, jedno vlákno), Dispatchers.IO (ve výchozím nastavení 64 vláken pro blokující operace: síť, soubory, databáze), Dispatchers.Default (rovný počtu jader CPU, pro intenzivní výpočty). Jejich kombinací prostřednictvím withContext vývojář přepíná mezi vlákny bez vytváření callbacků. withContext je funkce suspend, která nevrací řízení, dokud není úloha dokončena.
// Korutiny: kompozice úloh na pozadí
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
suspend fun loadUserProfile(userId: String): UserProfile =
coroutineScope {
// Paralelní načítání dat z různých zdrojů
val user = async(Dispatchers.IO) { fetchUser(userId) }
val posts = async(Dispatchers.IO) { fetchPosts(userId) }
val avatar = async(Dispatchers.Default) {
processAvatar(fetchAvatar(userId))
}
// await() — pozastaví se do dokončení všech úloh
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 }
Funkce loadUserProfile spouští tři paralelní úlohy na pozadí prostřednictvím async. fetchUser a fetchPosts — IO-bound (síť), provádějí se na Dispatchers.IO. processAvatar — CPU-bound (zpracování obrázku), provádí se na Dispatchers.Default. await() pozastaví korutinu do dokončení všech úloh. Celková doba provádění se rovná maximálnímu času ze tří úloh (500 ms pro fetchPosts), nikoli jejich součtu. To je klíčová výhoda coroutines oproti sekvenčnímu provádění.
Structured concurrency — princip, podle kterého má každá korutina nadřazený scope a zrušení rodiče automaticky ruší podřízené korutiny. V Androidu lifecycleScope ruší všechny korutiny při zničení Activity. viewModelScope — při čištění ViewModel. To zabraňuje únikům úloh na pozadí: pokud uživatel zavřel obrazovku, korutina na pozadí nebude pokračovat v načítání dat, která už nikdo nepotřebuje.
WorkManager — knihovna Android Jetpack pro provádění úloh na pozadí, které musí být provedeny i po restartu zařízení nebo zavření aplikace. Na rozdíl od Executors a korutin, které žijí v procesu aplikace, WorkManager předá úlohu systémovému dispečerovi, který zaručuje provedení za vhodných podmínek (dostupnost sítě, úroveň baterie, volné místo). WorkManager je vhodný pro synchronizaci dat, nahrávání logů, zálohování.
Úloha ve WorkManageru je třída, která dědí Worker (nebo CoroutineWorker pro korutiny). Worker.doWork() se provádí na vlákně na pozadí poskytnutém WorkManagerem. Výsledek se vrací prostřednictvím Result.success(), Result.retry() nebo Result.failure(). Úlohy lze kombinovat do řetězců: oneTimeWorkRequest.andThen(nextRequest).enqueue(). WorkManager sám vybírá optimální čas provedení s ohledem na omezení (Constraints).
// WorkManager s korutinami
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 {
// Provádí se na Dispatchers.Default (výchozí)
return withContext(Dispatchers.IO) {
try {
syncDataToServer()
Result.success()
} catch (e: Exception) {
if (runAttemptCount < 3) Result.retry() else Result.failure()
}
}
}
private suspend fun syncDataToServer() {
// Simulace synchronizace
delay(1000)
}
}
// Spuštění úlohy WorkManager s omezeními
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 dědí CoroutineWorker — verzi Workeru podporující korutiny. doWork() se provádí na Dispatchers.Default, přepnutí na IO pro síťové operace prostřednictvím withContext. Constraints zaručují, že synchronizace začne pouze při dostupnosti sítě a úrovni baterie ne nižší než nízká. BackoffCriteria s EXPONENTIAL zvyšuje interval mezi opakovanými pokusy: 10, 20, 40 sekund.
Pro pravidelné úlohy (synchronizace každých 15 minut, odesílání analýz každou hodinu) WorkManager poskytuje PeriodicWorkRequestBuilder. Minimální interval — 15 minut. Na rozdíl od OneTimeWorkRequest, PeriodicWorkRequest nezaručuje přesné dodržení intervalu — systém může zkombinovat několik periodických úloh pro úsporu baterie. Pro přesné intervaly použijte AlarmManager, ale zohledněte omezení Android 12+ pro přesné budíky.
První chyba — vytváření nového Thread pro každou úlohu. new Thread().start() vytváří nativní vlákno s alokací ~1 MB pro zásobník. Pro 100 paralelních úloh to je 100 MB jen pro zásobníky, plus režijní náklady na context switch. Používejte fondy vláken: Executors.newFixedThreadPool(n) (Android) nebo DispatchQueue.global() (iOS) — ty znovu používají vlákna a desetinásobně snižují režii.
Druhá chyba — přístup k měnitelnému stavu z několika vláken na pozadí bez synchronizace. Pokud dvě vlákna na pozadí současně zapisují do stejné ArrayList nebo HashMap, vzniká race condition: ConcurrentModificationException v Androidu, poškození dat v iOS. Řešení: používejte thread-safe kolekce (ConcurrentHashMap, CopyOnWriteArrayList) nebo serializujte přístup přes jednu frontu (DispatchQueue serial).
Třetí chyba — úlohy na pozadí bez správy životního cyklu. Spuštění korutiny v globálním scope bez vazby na životní cyklus Activity nebo ViewModel vede k únikům: úloha pokračuje po zničení obrazovky. V Androidu používejte lifecycleScope (Activity/Fragment) nebo viewModelScope (ViewModel). V iOS — weak self v closure a rušení úloh při deinit.
Často kladené otázky
Background Thread — vlákno, na kterém se provádějí operace nesouvisející s UI: síťové požadavky, čtení/zápis souborů, zpracování JSON, výpočty. Uvolňuje Main Thread od těžké práce a udržuje responzivitu rozhraní. V iOS jsou vlákna na pozadí spravována pomocí GCD (DispatchQueue.global), v Androidu — pomocí Executors nebo Kotlin Coroutines (Dispatchers.IO, Dispatchers.Default).
Dispatchers.IO je určen pro blokující operace vstupu-výstupu: čtení souborů, síťové požadavky, práce s databází. Má fond 64 vláken. Dispatchers.Default — pro úlohy náročné na CPU: třídění, filtrování, zpracování obrázků. Jeho fond se rovná počtu jader CPU. Použití Dispatchers.Default pro IO operace může zablokovat všechna jádra a Dispatchers.IO pro CPU úlohy může vytvořit nadměrný počet vláken.
DispatchQueue.global(qos: .background).async { } odešle blok provádění do globální fronty na pozadí. Po dokončení práce na pozadí je třeba vrátit se na hlavní vlákno pomocí DispatchQueue.main.async { } pro aktualizaci UI. Pro sekvenční úlohy na pozadí použijte OperationQueue s maxConcurrentOperationCount = 1 nebo DispatchQueue(label: "serial").
Doporučený počet vláken na pozadí se rovná počtu jader CPU plus 1 pro IO-bound úlohy. Na moderním 8jádrovém zařízení je to 9 vláken. Vytváření stovek vláken vede k thread starvation: operační systém tráví více času přepínáním kontextu (context switch) než prováděním úloh. GCD v iOS a Executors v Androidu automaticky optimalizují fond vláken pro aktuální zařízení.
V Kotlin Coroutines se návrat na Main Thread děje automaticky, pokud byla korutina spuštěna v Main-scope (lifecycleScope.launch, viewModelScope.launch). Funkce withContext(Dispatchers.IO) pozastaví korutinu na IO vlákně a po dokončení ji automaticky obnoví na dispatcheru, kde byla spuštěna (obvykle Main). Explicitní volání DispatchQueue.main.async není vyžadováno.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také