Background Thread en desarrollo móvil: qué es, tareas y formas de uso

Autor: IT Sectr Publicado: 2026-03-15 Tiempo de lectura: 11 min

Background Thread — un hilo de ejecución no vinculado a la interfaz de usuario, diseñado para operaciones de larga duración: solicitudes de red, trabajo con archivos, análisis JSON, compresión de imágenes, cifrado y consultas a bases de datos. En iOS, los hilos en segundo plano se gestionan mediante GCD (DispatchQueue.global) y OperationQueue; en Android, mediante Executors, WorkManager y Kotlin Coroutines (Dispatchers.IO, Dispatchers.Default). Según la Documentación de Apple DispatchQueue, al finalizar una operación en segundo plano, el resultado debe devolverse al Main Thread para actualizar la interfaz.

Puntos clave

  • Background Thread realiza operaciones que bloquean la UI: red, archivos, JSON, cálculos
  • iOS: DispatchQueue.global(qos:) y OperationQueue para tareas en segundo plano
  • Android: Dispatchers.IO (red/archivos), Dispatchers.Default (cálculos), WorkManager (tareas en segundo plano)
  • Corrutinas — el estándar moderno para trabajo en segundo plano: withContext(Dispatchers.IO) cambia de hilo sin callback hell
  • Resultado del Background Thread siempre se devuelve al Main Thread para actualizar la UI

Qué es un Background Thread

Background Thread — cualquier hilo en una aplicación que no sea el Main Thread y no tenga acceso a la UI. Su tarea es liberar al hilo principal de operaciones pesadas para que la interfaz se mantenga receptiva. El sistema operativo distribuye los hilos en segundo plano entre los núcleos de la CPU, permitiendo ejecutar varias tareas en paralelo. iOS gestiona automáticamente el pool de hilos mediante GCD; Android, mediante los pools de Java Executors.

A diferencia del Main Thread, que procesa eventos secuencialmente (uno tras otro), los hilos en segundo plano pueden ejecutarse en paralelo, limitados solo por la cantidad de núcleos de CPU. Por ejemplo, en un dispositivo de 8 núcleos se pueden ejecutar hasta 8 tareas en segundo plano sin ralentización significativa. Sin embargo, una cantidad excesiva de hilos (cientos) provoca thread starvation — competencia por los núcleos y aumento de la sobrecarga por cambio de contexto (context switch).

Quality of Service (QoS) — mecanismo de iOS que permite especificar la prioridad de una tarea en segundo plano. Valores: .userInteractive (el más alto, casi Main Thread), .userInitiated (el usuario espera un resultado), .default (estándar), .utility (el usuario no espera directamente), .background (el más bajo, para sincronización e indexación). En Android, el equivalente es Thread.setPriority() de 1 a 10, pero Android también usa cgroups para la gestión grupal de prioridades de hilos.

Background Thread en iOS: GCD y DispatchQueue.global

DispatchQueue.global(qos:) — la forma principal de obtener una cola en segundo plano en iOS. GCD (Grand Central Dispatch) crea automáticamente un pool de hilos y distribuye las tareas entre los núcleos. La llamada DispatchQueue.global(qos: .background).async {} envía un bloque a la cola en segundo plano con la prioridad más baja. Para tareas cuyo resultado se necesita de inmediato, use .userInitiated o .utility.

OperationQueue — una abstracción de más alto nivel sobre GCD que permite establecer dependencias entre operaciones, el número máximo de operaciones concurrentes (maxConcurrentOperationCount) y prioridades. OperationQueue es útil para cadenas multitarea complejas: descargar archivo → descomprimir → guardar en caché. Por defecto, OperationQueue usa hilos en segundo plano a menos que se indique lo contrario.

swift
import UIKit

class ImageDownloader {

    func downloadImagesSequentially() {
        let urls = ["https://example.com/1.png", "https://example.com/2.png"]

        // OperationQueue con 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("Descargado: \(url.lastPathComponent)")
                }
            }
        }
    }

    // GCD: cola global en segundo plano con diferentes QoS
    func backgroundTaskWithQoS() {
        DispatchQueue.global(qos: .userInitiated).async {
            // Prioridad alta — el usuario espera el resultado
            let result = self.heavyComputation()
            DispatchQueue.main.async {
                self.showResult(result)
            }
        }
    }

    private func heavyComputation() -> String {
        Thread.sleep(forTimeInterval: 2) // simulando trabajo
        return "Resultado del cálculo"
    }

    private func showResult(_ result: String) {
        print("Result on Main: \(result)")
    }
}

En el ejemplo, OperationQueue carga dos imágenes en paralelo (maxConcurrentOperationCount = 2) con QoS de fondo mediante qualityOfService = .utility. El método GCD backgroundTaskWithQoS usa una cola global con .userInitiated para una tarea cuyo resultado espera el usuario. Ambos enfoques finalizan regresando a DispatchQueue.main para actualizar la UI — este es un requisito obligatorio en iOS.

Colas en segundo plano Serial vs Concurrent

GCD admite dos tipos de colas: seriales y concurrentes. Las colas seriales ejecutan tareas una tras otra — esto es conveniente para acceder a un recurso compartido (archivo, BD) sin bloqueos. Las colas concurrentes ejecutan tareas en paralelo, distribuyéndolas entre los núcleos disponibles. DispatchQueue.global siempre es concurrente. Para crear una cola serial, use DispatchQueue(label: "com.app.queue").

Background Thread en Android: Executors y Dispatchers

Android proporciona varios niveles de abstracción para hilos en segundo plano. El enfoque clásico es java.util.concurrent.Executors.newFixedThreadPool(n) o Executors.newCachedThreadPool(). El enfoque moderno es Kotlin Coroutines con Dispatchers.IO (para E/S: red, archivos, BD) y Dispatchers.Default (para tareas intensivas de CPU: ordenamiento, procesamiento de imágenes). WorkManager es para tareas en segundo plano diferidas y garantizadas.

HandlerThread — una clase especializada de Android para crear un hilo en segundo plano con su propio Looper (cola de mensajes). A diferencia de Executors, HandlerThread permite enviar mensajes y Runnables a través de un Handler. Se usa para operaciones que requieren encolado (por ejemplo, escritura secuencial en BD). Después de su uso, se debe llamar a quit() o quitSafely() para liberar recursos.

kotlin
// Android: Executors y Dispatchers de Corrutinas
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.concurrent.Executors

class DataRepository {

    private val ioExecutor = Executors.newFixedThreadPool(4)

    // Enfoque clásico mediante Executors
    fun loadDataLegacy(callback: (String) -> Unit) {
        ioExecutor.execute {
            val result = readFromFile()
            val handler = android.os.Handler(android.os.Looper.getMainLooper())
            handler.post { callback(result) }
        }
    }

    // Enfoque moderno mediante Corrutinas
    suspend fun loadDataCoroutines(): String {
        return withContext(Dispatchers.IO) {
            // Operación de archivo — ejecutando en pool en segundo plano
            readFromFile()
        }
        // Resultado regresa automáticamente a Dispatchers.Main
    }

    // Tarea intensiva de CPU en Dispatchers.Default
    suspend fun processImage(pixels: IntArray): IntArray {
        return withContext(Dispatchers.Default) {
            // Ordenamiento, filtrado — ejecutando en pool Default
            pixels.sortedArray()
        }
    }

    private fun readFromFile(): String {
        Thread.sleep(1000) // simulando lectura de archivo
        return "file_content"
    }

    fun cleanup() {
        ioExecutor.shutdown()
    }
}

El ejemplo de DataRepository muestra la evolución de los hilos en segundo plano en Android. El método heredado loadDataLegacy usa Executors.newFixedThreadPool(4) con un Handler para regresar al Main Thread. El método moderno loadDataCoroutines usa withContext(Dispatchers.IO) — la corrutina se suspende durante la ejecución sin bloquear el hilo y se reanuda automáticamente en el Main Thread. Dispatchers.Default se recomienda para operaciones intensivas de CPU (ordenamiento, filtrado, transformación de datos).

Corrutinas como estándar moderno de tareas en segundo plano

Kotlin Coroutines — no solo una forma de trabajar con hilos, sino un modelo fundamentalmente diferente: las tareas asincrónicas no están vinculadas a un hilo específico y pueden suspenderse sin bloquear. Esto significa que mientras está en segundo plano, una corrutina no ocupa un hilo, sino que lo libera para otras tareas. El mecanismo de suspensión permite ejecutar cientos de miles de tareas concurrentes en un pool de 4–8 hilos sin thread starvation.

Tres dispatchers principales: Dispatchers.Main (UI, un hilo), Dispatchers.IO (64 hilos por defecto para operaciones bloqueantes: red, archivos, BD), Dispatchers.Default (igual al número de núcleos de CPU, para cálculos intensivos). Combinándolos mediante withContext, el desarrollador cambia entre hilos sin crear callbacks. withContext es una función suspend que no devuelve el control hasta que la tarea se completa.

kotlin
// Corrutinas: composición de tareas en segundo plano
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay

suspend fun loadUserProfile(userId: String): UserProfile =
    coroutineScope {
        // Carga paralela de datos desde diferentes fuentes
        val user = async(Dispatchers.IO) { fetchUser(userId) }
        val posts = async(Dispatchers.IO) { fetchPosts(userId) }
        val avatar = async(Dispatchers.Default) {
            processAvatar(fetchAvatar(userId))
        }

        // await() — se suspende hasta que todas las tareas se completan
        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 }

La función loadUserProfile lanza tres tareas en segundo plano en paralelo mediante async. fetchUser y fetchPosts son de E/S (red), se ejecutan en Dispatchers.IO. processAvatar es intensiva de CPU (procesamiento de imagen), se ejecuta en Dispatchers.Default. await() suspende la corrutina hasta que todas las tareas se completan. El tiempo total de ejecución es igual al tiempo máximo entre las tres tareas (500 ms para fetchPosts), no su suma. Esta es una ventaja clave de las corrutinas sobre la ejecución secuencial.

Concurrencia estructurada: prevención de fugas

Structured concurrency — principio por el cual cada corrutina tiene un ámbito padre, y cancelar al padre cancela automáticamente las corrutinas hijas. En Android, lifecycleScope cancela todas las corrutinas al destruirse la Activity. viewModelScope lo hace al limpiarse el ViewModel. Esto previene fugas de tareas en segundo plano: si el usuario cierra la pantalla, mientras está en segundo plano la corrutina no continuará cargando datos que ya no son útiles.

WorkManager: tareas en segundo plano para Android

WorkManager — una biblioteca de Android Jetpack para ejecutar tareas en segundo plano que deben completarse incluso después de reiniciar el dispositivo o cerrar la aplicación. A diferencia de Executors y corrutinas, que viven dentro del proceso de la aplicación, WorkManager entrega la tarea a un despachador del sistema que garantiza su ejecución bajo condiciones adecuadas (disponibilidad de red, carga de batería, espacio libre). WorkManager es adecuado para sincronización de datos, carga de registros y copias de seguridad.

Una tarea en WorkManager es una clase que extiende Worker (o CoroutineWorker para corrutinas). Worker.doWork() se ejecuta en un hilo en segundo plano proporcionado por WorkManager. El resultado se devuelve mediante Result.success(), Result.retry() o Result.failure(). Las tareas se pueden encadenar: oneTimeWorkRequest.andThen(nextRequest).enqueue(). WorkManager elige el momento óptimo de ejecución considerando las restricciones.

kotlin
// WorkManager con corrutinas
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 {
        // Ejecutando en Dispatchers.Default (por defecto)
        return withContext(Dispatchers.IO) {
            try {
                syncDataToServer()
                Result.success()
            } catch (e: Exception) {
                if (runAttemptCount < 3) Result.retry() else Result.failure()
            }
        }
    }

    private suspend fun syncDataToServer() {
        // Simulación de sincronización
        delay(1000)
    }
}

// Lanzamiento de tarea WorkManager con restricciones
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 extiende CoroutineWorker — una versión de Worker compatible con corrutinas. doWork() se ejecuta en Dispatchers.Default, cambiando a IO para operaciones de red mediante withContext. Las Constraints garantizan que la sincronización solo se ejecute cuando haya red y la batería no esté por debajo de un nivel bajo. BackoffCriteria con EXPONENTIAL aumenta el intervalo entre reintentos: 10, 20, 40 segundos.

PeriodicWorkRequest para tareas periódicas en segundo plano

Para tareas periódicas (sincronización cada 15 minutos, envío de analíticas cada hora), WorkManager proporciona PeriodicWorkRequestBuilder. El intervalo mínimo es de 15 minutos. A diferencia de OneTimeWorkRequest, PeriodicWorkRequest no garantiza el cumplimiento exacto del intervalo — el sistema puede agrupar varias tareas periódicas para ahorrar batería. Para intervalos precisos, use AlarmManager, pero tenga en cuenta las restricciones de Android 12+ sobre alarmas exactas.

Errores típicos al trabajar con hilos en segundo plano

Primer error — crear un nuevo Thread para cada tarea. new Thread().start() crea un hilo nativo asignando ~1 MB para la pila. Para 100 tareas en paralelo, eso es 100 MB solo para pilas, más la sobrecarga de cambio de contexto. Use pools de hilos: Executors.newFixedThreadPool(n) (Android) o DispatchQueue.global() (iOS) — reutilizan hilos, reduciendo la sobrecarga en órdenes de magnitud.

Segundo error — acceder a estado mutable desde múltiples hilos en segundo plano sin sincronización. Si dos hilos en segundo plano escriben simultáneamente en el mismo ArrayList o HashMap, ocurren race conditions: ConcurrentModificationException en Android, corrupción de datos en iOS. Solución: use colecciones thread-safe (ConcurrentHashMap, CopyOnWriteArrayList) o serialice el acceso mediante una sola cola (DispatchQueue serial).

Tercer error — tareas en segundo plano sin gestión del ciclo de vida. Lanzar una corrutina en un ámbito global sin vincularla al ciclo de vida de Activity o ViewModel provoca fugas: la tarea continúa ejecutándose después de destruir la pantalla. En Android, use lifecycleScope (Activity/Fragment) o viewModelScope (ViewModel). En iOS, use weak self en los closures y cancele tareas al hacer deinit.

Preguntas frecuentes

¿Qué es un Background Thread en aplicaciones móviles?

Background Thread — un hilo en el que se ejecutan operaciones no relacionadas con la UI: solicitudes de red, lectura/escritura de archivos, análisis JSON, cálculos. Libera al Main Thread de trabajo pesado, manteniendo la interfaz receptiva. En iOS, los hilos en segundo plano se gestionan mediante GCD (DispatchQueue.global); en Android, mediante Executors o Kotlin Coroutines (Dispatchers.IO, Dispatchers.Default).

¿Cuál es la diferencia entre Dispatchers.IO y Dispatchers.Default?

Dispatchers.IO está diseñado para operaciones de E/S bloqueantes: lectura de archivos, solicitudes de red, trabajo con BD. Tiene un pool de 64 hilos. Dispatchers.Default es para tareas intensivas de CPU: ordenamiento, filtrado, procesamiento de imágenes. Su pool es igual al número de núcleos de CPU. Usar Dispatchers.Default para operaciones de E/S puede bloquear todos los núcleos, y Dispatchers.IO para tareas de CPU puede crear una cantidad excesiva de hilos.

¿Cómo cambiar a un hilo en segundo plano en iOS?

DispatchQueue.global(qos: .background).async { } envía un bloque a la cola global en segundo plano. Después de completar el trabajo en segundo plano, debe regresar al hilo principal mediante DispatchQueue.main.async { } para actualizar la UI. Para tareas secuenciales en segundo plano, use OperationQueue con maxConcurrentOperationCount = 1 o DispatchQueue(label: "serial").

¿Cuántos hilos en segundo plano se pueden crear en una aplicación móvil?

La cantidad recomendada de hilos en segundo plano es igual al número de núcleos de CPU más 1 para tareas de E/S. En un dispositivo moderno de 8 núcleos, eso son 9 hilos. Crear cientos de hilos provoca thread starvation: el SO gasta más tiempo en cambio de contexto que en ejecutar tareas. GCD en iOS y Executors en Android optimizan automáticamente el pool de hilos para el dispositivo actual.

¿Es necesario regresar al Main Thread después de una corrutina?

En Kotlin Coroutines, el retorno al Main Thread ocurre automáticamente si la corrutina se lanzó en un ámbito Main (lifecycleScope.launch, viewModelScope.launch). La función withContext(Dispatchers.IO) suspende la corrutina en un hilo IO, y al finalizar se reanuda automáticamente en el dispatcher donde se lanzó (generalmente Main). No se requiere una llamada explícita a DispatchQueue.main.async.

Resumen

  • Background Thread — un hilo para operaciones que no deben ejecutarse en el Main Thread: red, archivos, análisis JSON, cálculos
  • iOS: DispatchQueue.global(qos:) y OperationQueue son las principales API para tareas en segundo plano con soporte QoS
  • Android: Executors, HandlerThread, WorkManager para Java; Dispatchers.IO/Default + corrutinas para Kotlin
  • Corrutinas con withContext cambian de hilo sin callbacks y sin bloqueo (mecanismo suspend)
  • WorkManager garantiza la ejecución de tareas en segundo plano incluso después de reiniciar el dispositivo, respetando restricciones
  • Errores: crear nuevos Threads en lugar de usar un pool, race conditions al acceder a estado mutable, fugas por falta de vinculación al ciclo de vida
  • Resultado del hilo en segundo plano siempre se devuelve al Main Thread: mediante Dispatchers.Main (Android) o DispatchQueue.main.async (iOS)

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también