Background Thread — uma thread de execução não vinculada à interface do usuário, projetada para operações de longa duração: requisições de rede, trabalho com arquivos, análise JSON, compressão de imagens, criptografia e consultas a banco de dados. No iOS, as threads em segundo plano são gerenciadas através de GCD (DispatchQueue.global) e OperationQueue; no Android, através de Executors, WorkManager e Kotlin Coroutines (Dispatchers.IO, Dispatchers.Default). De acordo com a Documentação da Apple DispatchQueue, após a conclusão de uma operação em segundo plano, o resultado deve ser retornado à Main Thread para atualizar a interface.
Pontos principais
Background Thread — qualquer thread em um aplicativo que não seja a Main Thread e não tenha acesso à UI. Sua tarefa é liberar a thread principal de operações pesadas para que a interface permaneça responsiva. O sistema operacional distribui as threads em segundo plano pelos núcleos da CPU, permitindo executar várias tarefas em paralelo. O iOS gerencia automaticamente o pool de threads através do GCD; o Android, através dos pools de Java Executors.
Ao contrário da Main Thread, que processa eventos sequencialmente (um após o outro), as threads em segundo plano podem executar em paralelo, limitadas apenas pelo número de núcleos da CPU. Por exemplo, em um dispositivo de 8 núcleos, é possível executar até 8 tarefas em segundo plano sem desaceleração significativa. No entanto, uma quantidade excessiva de threads (centenas) leva ao thread starvation — competição pelos núcleos e aumento da sobrecarga de troca de contexto (context switch).
Quality of Service (QoS) — mecanismo do iOS que permite especificar a prioridade de uma tarefa em segundo plano. Valores: .userInteractive (o mais alto, quase Main Thread), .userInitiated (o usuário aguarda o resultado), .default (padrão), .utility (o usuário não espera diretamente), .background (o mais baixo, para sincronização e indexação). No Android, o equivalente é Thread.setPriority() de 1 a 10, mas o Android também usa cgroups para gerenciamento em grupo de prioridades de threads.
DispatchQueue.global(qos:) — a principal forma de obter uma fila em segundo plano no iOS. O GCD (Grand Central Dispatch) cria automaticamente um pool de threads e distribui as tarefas pelos núcleos. A chamada DispatchQueue.global(qos: .background).async {} envia um bloco para a fila em segundo plano com a prioridade mais baixa. Para tarefas cujo resultado é necessário imediatamente, use .userInitiated ou .utility.
OperationQueue — uma abstração de mais alto nível sobre o GCD que permite definir dependências entre operações, o número máximo de operações simultâneas (maxConcurrentOperationCount) e prioridades. A OperationQueue é útil para cadeias multitarefa complexas: baixar arquivo → descompactar → salvar em cache. Por padrão, a OperationQueue usa threads em segundo plano, a menos que especificado de outra forma.
import UIKit
class ImageDownloader {
func downloadImagesSequentially() {
let urls = ["https://example.com/1.png", "https://example.com/2.png"]
// OperationQueue com 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("Carregado: \(url.lastPathComponent)")
}
}
}
}
// GCD: fila global em segundo plano com diferentes QoS
func backgroundTaskWithQoS() {
DispatchQueue.global(qos: .userInitiated).async {
// Prioridade alta — o usuário aguarda o resultado
let result = self.heavyComputation()
DispatchQueue.main.async {
self.showResult(result)
}
}
}
private func heavyComputation() -> String {
Thread.sleep(forTimeInterval: 2) // simulando trabalho
return "Resultado do cálculo"
}
private func showResult(_ result: String) {
print("Result on Main: \(result)")
}
}
No exemplo, a OperationQueue carrega duas imagens em paralelo (maxConcurrentOperationCount = 2) com QoS de fundo via qualityOfService = .utility. O método GCD backgroundTaskWithQoS usa uma fila global com .userInitiated para uma tarefa cujo resultado o usuário está aguardando. Ambas as abordagens terminam retornando à DispatchQueue.main para atualizar a UI — este é um requisito obrigatório no iOS.
O GCD suporta dois tipos de filas: seriais e concorrentes. Filas seriais executam tarefas uma após a outra — isso é conveniente para acessar um recurso compartilhado (arquivo, BD) sem bloqueios. Filas concorrentes executam tarefas em paralelo, distribuindo-as pelos núcleos disponíveis. DispatchQueue.global é sempre concorrente. Para criar uma fila serial, use DispatchQueue(label: "com.app.queue").
Android fornece vários níveis de abstração para threads em segundo plano. A abordagem clássica é java.util.concurrent.Executors.newFixedThreadPool(n) ou Executors.newCachedThreadPool(). A abordagem moderna são as Kotlin Coroutines com Dispatchers.IO (para E/S: rede, arquivos, BD) e Dispatchers.Default (para tarefas intensivas de CPU: ordenação, processamento de imagens). WorkManager é para tarefas em segundo plano adiadas e garantidas.
HandlerThread — uma classe especializada do Android para criar uma thread em segundo plano com seu próprio Looper (fila de mensagens). Ao contrário dos Executors, HandlerThread permite enviar mensagens e Runnables através de um Handler. É usado para operações que exigem enfileiramento (por exemplo, escrita sequencial em BD). Após o uso, deve-se chamar quit() ou quitSafely() para liberar recursos.
// Android: Executors e Dispatchers de Corrotinas
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.concurrent.Executors
class DataRepository {
private val ioExecutor = Executors.newFixedThreadPool(4)
// Abordagem clássica 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) }
}
}
// Abordagem moderna via Corrotinas
suspend fun loadDataCoroutines(): String {
return withContext(Dispatchers.IO) {
// Operação de arquivo — executando no pool em segundo plano
readFromFile()
}
// Resultado retorna automaticamente ao Dispatchers.Main
}
// Tarefa intensiva de CPU no Dispatchers.Default
suspend fun processImage(pixels: IntArray): IntArray {
return withContext(Dispatchers.Default) {
// Ordenação, filtragem — executando no pool Default
pixels.sortedArray()
}
}
private fun readFromFile(): String {
Thread.sleep(1000) // simulando leitura de arquivo
return "file_content"
}
fun cleanup() {
ioExecutor.shutdown()
}
}
O exemplo DataRepository mostra a evolução das threads em segundo plano no Android. O método legado loadDataLegacy usa Executors.newFixedThreadPool(4) com um Handler para retornar à Main Thread. O método moderno loadDataCoroutines usa withContext(Dispatchers.IO) — a corrotina é suspensa durante a execução sem bloquear a thread e retoma automaticamente na Main Thread. Dispatchers.Default é recomendado para operações intensivas de CPU (ordenação, filtragem, transformação de dados).
Kotlin Coroutines — não apenas uma forma de trabalhar com threads, mas um modelo fundamentalmente diferente: tarefas assíncronas não estão vinculadas a uma thread específica e podem ser suspensas sem bloquear. Isso significa que, enquanto está em segundo plano, uma corrotina não ocupa uma thread, mas a libera para outras tarefas. O mecanismo de suspensão permite executar centenas de milhares de tarefas concorrentes em um pool de 4–8 threads sem thread starvation.
Três dispatchers principais: Dispatchers.Main (UI, uma thread), Dispatchers.IO (64 threads por padrão para operações bloqueantes: rede, arquivos, BD), Dispatchers.Default (igual ao número de núcleos da CPU, para cálculos intensivos). Combinando-os através de withContext, o desenvolvedor alterna entre threads sem criar callbacks. withContext é uma função suspend que não retorna o controle até que a tarefa seja concluída.
// Corrotinas: composição de tarefas em segundo plano
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
suspend fun loadUserProfile(userId: String): UserProfile =
coroutineScope {
// Carregamento paralelo de dados de diferentes fontes
val user = async(Dispatchers.IO) { fetchUser(userId) }
val posts = async(Dispatchers.IO) { fetchPosts(userId) }
val avatar = async(Dispatchers.Default) {
processAvatar(fetchAvatar(userId))
}
// await() — suspende até que todas as tarefas sejam concluídas
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 }
A função loadUserProfile lança três tarefas em segundo plano em paralelo através de async. fetchUser e fetchPosts são de E/S (rede), executadas em Dispatchers.IO. processAvatar é intensiva de CPU (processamento de imagem), executada em Dispatchers.Default. await() suspende a corrotina até que todas as tarefas sejam concluídas. O tempo total de execução é igual ao tempo máximo entre as três tarefas (500 ms para fetchPosts), não a soma delas. Esta é uma vantagem fundamental das corrotinas sobre a execução sequencial.
Structured concurrency — princípio pelo qual cada corrotina tem um escopo pai, e cancelar o pai cancela automaticamente as corrotinas filhas. No Android, lifecycleScope cancela todas as corrotinas quando a Activity é destruída. viewModelScope faz isso quando o ViewModel é limpo. Isso evita vazamentos de tarefas em segundo plano: se o usuário fechar a tela, enquanto estiver em segundo plano a corrotina não continuará carregando dados que não são mais necessários.
WorkManager — uma biblioteca Android Jetpack para executar tarefas em segundo plano que devem ser concluídas mesmo após reiniciar o dispositivo ou fechar o aplicativo. Ao contrário de Executors e corrotinas, que vivem dentro do processo do aplicativo, o WorkManager entrega a tarefa a um despachante do sistema que garante a execução sob condições adequadas (disponibilidade de rede, carga da bateria, espaço livre). O WorkManager é adequado para sincronização de dados, envio de logs e backup.
Uma tarefa no WorkManager é uma classe que estende Worker (ou CoroutineWorker para corrotinas). Worker.doWork() executa em uma thread em segundo plano fornecida pelo WorkManager. O resultado é retornado via Result.success(), Result.retry() ou Result.failure(). As tarefas podem ser encadeadas: oneTimeWorkRequest.andThen(nextRequest).enqueue(). O WorkManager escolhe o momento ideal de execução considerando as restrições.
// WorkManager com corrotinas
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 {
// Executando em Dispatchers.Default (por padrão)
return withContext(Dispatchers.IO) {
try {
syncDataToServer()
Result.success()
} catch (e: Exception) {
if (runAttemptCount < 3) Result.retry() else Result.failure()
}
}
}
private suspend fun syncDataToServer() {
// Simulação de sincronização
delay(1000)
}
}
// Iniciando tarefa WorkManager com restrições
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 estende CoroutineWorker — uma versão do Worker que suporta corrotinas. doWork() executa em Dispatchers.Default, alternando para IO para operações de rede através de withContext. As Constraints garantem que a sincronização só seja executada quando a rede estiver disponível e a bateria não estiver abaixo de um nível baixo. BackoffCriteria com EXPONENTIAL aumenta o intervalo entre tentativas: 10, 20, 40 segundos.
Para tarefas periódicas (sincronização a cada 15 minutos, envio de análises a cada hora), o WorkManager fornece PeriodicWorkRequestBuilder. O intervalo mínimo é de 15 minutos. Ao contrário do OneTimeWorkRequest, o PeriodicWorkRequest não garante o cumprimento exato do intervalo — o sistema pode agrupar várias tarefas periódicas para economizar bateria. Para intervalos precisos, use AlarmManager, mas considere as restrições do Android 12+ sobre alarmes exatos.
Primeiro erro — criar uma nova Thread para cada tarefa. new Thread().start() cria uma thread nativa alocando ~1 MB para a pilha. Para 100 tarefas em paralelo, são 100 MB apenas para pilhas, mais a sobrecarga de troca de contexto. Use pools de threads: Executors.newFixedThreadPool(n) (Android) ou DispatchQueue.global() (iOS) — eles reutilizam threads, reduzindo a sobrecarga em ordens de magnitude.
Segundo erro — acessar estado mutável de várias threads em segundo plano sem sincronização. Se duas threads em segundo plano escrevem simultaneamente no mesmo ArrayList ou HashMap, ocorrem condições de corrida: ConcurrentModificationException no Android, corrupção de dados no iOS. Solução: use coleções thread-safe (ConcurrentHashMap, CopyOnWriteArrayList) ou serializa o acesso através de uma única fila (DispatchQueue serial).
Terceiro erro — tarefas em segundo plano sem gerenciamento de ciclo de vida. Iniciar uma corrotina em um escopo global sem vinculá-la ao ciclo de vida da Activity ou ViewModel causa vazamentos: a tarefa continua executando após a destruição da tela. No Android, use lifecycleScope (Activity/Fragment) ou viewModelScope (ViewModel). No iOS, use weak self em closures e cancele tarefas no deinit.
Perguntas frequentes
Background Thread — uma thread na qual são executadas operações não relacionadas à UI: requisições de rede, leitura/escrita de arquivos, análise JSON, cálculos. Ela libera a Main Thread de trabalho pesado, mantendo a interface responsiva. No iOS, as threads em segundo plano são gerenciadas através do GCD (DispatchQueue.global); no Android, através de Executors ou Kotlin Coroutines (Dispatchers.IO, Dispatchers.Default).
Dispatchers.IO é projetado para operações de E/S bloqueantes: leitura de arquivos, requisições de rede, trabalho com BD. Tem um pool de 64 threads. Dispatchers.Default é para tarefas intensivas de CPU: ordenação, filtragem, processamento de imagens. Seu pool é igual ao número de núcleos da CPU. Usar Dispatchers.Default para operações de E/S pode bloquear todos os núcleos, e Dispatchers.IO para tarefas de CPU pode criar uma quantidade excessiva de threads.
DispatchQueue.global(qos: .background).async { } envia um bloco para a fila global em segundo plano. Após concluir o trabalho em segundo plano, é necessário retornar à thread principal através de DispatchQueue.main.async { } para atualizar a UI. Para tarefas sequenciais em segundo plano, use OperationQueue com maxConcurrentOperationCount = 1 ou DispatchQueue(label: "serial").
O número recomendado de threads em segundo plano é igual ao número de núcleos da CPU mais 1 para tarefas de E/S. Em um dispositivo moderno de 8 núcleos, são 9 threads. Criar centenas de threads leva ao thread starvation: o SO gasta mais tempo na troca de contexto do que na execução de tarefas. O GCD no iOS e os Executors no Android otimizam automaticamente o pool de threads para o dispositivo atual.
Em Kotlin Coroutines, o retorno à Main Thread ocorre automaticamente se a corrotina foi iniciada em um escopo Main (lifecycleScope.launch, viewModelScope.launch). A função withContext(Dispatchers.IO) suspende a corrotina em uma thread IO e, após a conclusão, retoma automaticamente no dispatcher onde foi iniciada (geralmente Main). Não é necessária uma chamada explícita a DispatchQueue.main.async.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também