Background Thread dans le développement mobile : définition, tâches et méthodes d'utilisation

Auteur : IT Sectr Publié le : 2026-03-15 Temps de lecture : 11 min

Background Thread — un thread d'exécution non lié à l'interface utilisateur, conçu pour les opérations longues : requêtes réseau, travail avec les fichiers, analyse JSON, compression d'images, chiffrement et requêtes de base de données. Sous iOS, les threads d'arrière-plan sont gérés via GCD (DispatchQueue.global) et OperationQueue ; sous Android, via Executors, WorkManager et Kotlin Coroutines (Dispatchers.IO, Dispatchers.Default). Selon la Documentation Apple DispatchQueue, après la fin d'une opération en arrière-plan, le résultat doit être retourné au Main Thread pour mettre à jour l'interface.

Points clés

  • Background Thread exécute les opérations qui bloquent l'UI : réseau, fichiers, JSON, calculs
  • iOS : DispatchQueue.global(qos:) et OperationQueue pour les tâches d'arrière-plan
  • Android : Dispatchers.IO (réseau/fichiers), Dispatchers.Default (calculs), WorkManager (tâches d'arrière-plan)
  • Coroutines — le standard moderne pour le travail en arrière-plan : withContext(Dispatchers.IO) change de thread sans callback hell
  • Résultat du Background Thread est toujours retourné au Main Thread pour mettre à jour l'UI

Qu'est-ce qu'un Background Thread

Background Thread — tout thread dans une application qui n'est pas le Main Thread et n'a pas accès à l'UI. Sa tâche est de libérer le thread principal des opérations lourdes pour que l'interface reste réactive. Le système d'exploitation répartit les threads d'arrière-plan sur les cœurs du processeur, permettant d'exécuter plusieurs tâches en parallèle. iOS gère automatiquement le pool de threads via GCD, Android via les pools Java Executors.

Contrairement au Main Thread qui traite les événements séquentiellement (un par un), les threads d'arrière-plan peuvent s'exécuter en parallèle, limités uniquement par le nombre de cœurs CPU. Par exemple, sur un appareil à 8 cœurs, on peut lancer jusqu'à 8 tâches d'arrière-plan parallèles sans ralentissement significatif. Cependant, un nombre excessif de threads (centaines) conduit à thread starvation — compétition pour les cœurs et augmentation des frais généraux de changement de contexte (context switch).

Quality of Service (QoS) — mécanisme iOS qui permet de spécifier la priorité d'une tâche d'arrière-plan. Valeurs : .userInteractive (la plus élevée, presque Main Thread), .userInitiated (l'utilisateur attend un résultat), .default (standard), .utility (l'utilisateur n'attend pas directement), .background (la plus basse, pour synchronisation et indexation). Sous Android, l'équivalent est Thread.setPriority() de 1 à 10, mais Android utilise également cgroups pour la gestion groupée des priorités des threads.

Background Thread sous iOS : GCD et DispatchQueue.global

DispatchQueue.global(qos:) — le moyen principal d'obtenir une file d'attente d'arrière-plan sous iOS. GCD (Grand Central Dispatch) crée automatiquement un pool de threads et répartit les tâches sur les cœurs. L'appel DispatchQueue.global(qos: .background).async {} envoie un bloc dans la file d'attente d'arrière-plan avec la priorité la plus basse. Pour les tâches dont le résultat est nécessaire immédiatement, utilisez .userInitiated ou .utility.

OperationQueue — une abstraction de plus haut niveau sur GCD qui permet de définir des dépendances entre opérations, le nombre maximum d'opérations simultanées (maxConcurrentOperationCount) et les priorités. OperationQueue est pratique pour les chaînes multitâches complexes : télécharger fichier → décompresser → sauvegarder en cache. Par défaut, OperationQueue utilise les threads d'arrière-plan sauf indication contraire.

swift
import UIKit

class ImageDownloader {

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

        // OperationQueue avec 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("Téléchargé : \(url.lastPathComponent)")
                }
            }
        }
    }

    // GCD : file d'attente globale d'arrière-plan avec différentes QoS
    func backgroundTaskWithQoS() {
        DispatchQueue.global(qos: .userInitiated).async {
            // Priorité élevée — l'utilisateur attend le résultat
            let result = self.heavyComputation()
            DispatchQueue.main.async {
                self.showResult(result)
            }
        }
    }

    private func heavyComputation() -> String {
        Thread.sleep(forTimeInterval: 2) // simulation de travail
        return "Résultat du calcul"
    }

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

Dans l'exemple, OperationQueue charge deux images en parallèle (maxConcurrentOperationCount = 2) avec QoS d'arrière-plan via qualityOfService = .utility. La méthode GCD backgroundTaskWithQoS utilise une file d'attente globale avec .userInitiated pour une tâche dont l'utilisateur attend le résultat. Les deux approches se terminent en retournant sur DispatchQueue.main pour mettre à jour l'UI — c'est une exigence obligatoire sous iOS.

Files d'attente d'arrière-plan Serial vs Concurrent

GCD prend en charge deux types de files d'attente : séries et concurrentes. Les files séries exécutent les tâches une par une — c'est pratique pour accéder à une ressource partagée (fichier, BD) sans verrous. Les files concurrentes exécutent les tâches en parallèle, les répartissant sur les cœurs disponibles. DispatchQueue.global est toujours concurrente. Pour créer une file série, utilisez DispatchQueue(label: "com.app.queue").

Background Thread sous Android : Executors et Dispatchers

Android propose plusieurs niveaux d'abstraction pour les threads d'arrière-plan. L'approche classique est java.util.concurrent.Executors.newFixedThreadPool(n) ou Executors.newCachedThreadPool(). L'approche moderne est Kotlin Coroutines avec Dispatchers.IO (pour les E/S : réseau, fichiers, BD) et Dispatchers.Default (pour les tâches intensives en CPU : tri, traitement d'images). WorkManager est pour les tâches d'arrière-plan différées et garanties.

HandlerThread — une classe spécialisée Android pour créer un thread d'arrière-plan avec son propre Looper (file de messages). Contrairement à Executors, HandlerThread permet d'envoyer des messages et des Runnables via un Handler. Il est utilisé pour les opérations qui nécessitent une mise en file d'attente (par exemple, écriture séquentielle en BD). Après utilisation, quit() ou quitSafely() doit être appelé pour libérer les ressources.

kotlin
// Android : Executors et Dispatchers de Coroutines
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.concurrent.Executors

class DataRepository {

    private val ioExecutor = Executors.newFixedThreadPool(4)

    // Approche classique 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) }
        }
    }

    // Approche moderne via Coroutines
    suspend fun loadDataCoroutines(): String {
        return withContext(Dispatchers.IO) {
            // Opération fichier — exécution dans le pool d'arrière-plan
            readFromFile()
        }
        // Résultat retourné automatiquement à Dispatchers.Main
    }

    // Tâche intensive en CPU sur Dispatchers.Default
    suspend fun processImage(pixels: IntArray): IntArray {
        return withContext(Dispatchers.Default) {
            // Tri, filtrage — exécution sur le pool Default
            pixels.sortedArray()
        }
    }

    private fun readFromFile(): String {
        Thread.sleep(1000) // simulation de lecture de fichier
        return "file_content"
    }

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

L'exemple DataRepository montre l'évolution des threads d'arrière-plan sous Android. La méthode legacy loadDataLegacy utilise Executors.newFixedThreadPool(4) avec un Handler pour retourner au Main Thread. La méthode moderne loadDataCoroutines utilise withContext(Dispatchers.IO) — la coroutine est suspendue pendant l'exécution sans bloquer le thread et reprend automatiquement sur le Main Thread. Dispatchers.Default est recommandé pour les opérations intensives en CPU (tri, filtrage, transformation de données).

Les Coroutines comme standard moderne des tâches d'arrière-plan

Kotlin Coroutines — pas seulement une façon de travailler avec les threads, mais un modèle fondamentalement différent : les tâches asynchrones ne sont pas liées à un thread spécifique et peuvent être suspendues sans bloquer. Cela signifie qu'en arrière-plan, une coroutine n'occupe pas un thread mais le libère pour d'autres tâches. Le mécanisme de suspension permet d'exécuter des centaines de milliers de tâches concurrentes sur un pool de 4–8 threads sans thread starvation.

Trois dispatchers principaux : Dispatchers.Main (UI, un thread), Dispatchers.IO (64 threads par défaut pour les opérations bloquantes : réseau, fichiers, BD), Dispatchers.Default (égal au nombre de cœurs CPU, pour les calculs intensifs). En les combinant via withContext, le développeur change de thread sans créer de callbacks. withContext est une fonction suspend qui ne rend pas le contrôle tant que la tâche n'est pas terminée.

kotlin
// Coroutines : composition de tâches d'arrière-plan
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay

suspend fun loadUserProfile(userId: String): UserProfile =
    coroutineScope {
        // Chargement parallèle de données depuis différentes sources
        val user = async(Dispatchers.IO) { fetchUser(userId) }
        val posts = async(Dispatchers.IO) { fetchPosts(userId) }
        val avatar = async(Dispatchers.Default) {
            processAvatar(fetchAvatar(userId))
        }

        // await() — suspend jusqu'à la fin de toutes les tâches
        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 fonction loadUserProfile lance trois tâches d'arrière-plan parallèles via async. fetchUser et fetchPosts sont liées aux E/S (réseau), exécutées sur Dispatchers.IO. processAvatar est intensive en CPU (traitement d'image), exécutée sur Dispatchers.Default. await() suspend la coroutine jusqu'à la fin de toutes les tâches. Le temps d'exécution total est égal au temps maximum parmi les trois tâches (500 ms pour fetchPosts), et non à leur somme. C'est un avantage clé des coroutines sur l'exécution séquentielle.

Concurrence structurée : prévention des fuites

Structured concurrency — principe selon lequel chaque coroutine a une portée parente, et l'annulation du parent annule automatiquement les coroutines enfants. Sous Android, lifecycleScope annule toutes les coroutines lors de la destruction de l'Activity. viewModelScope le fait lors du nettoyage du ViewModel. Cela évite les fuites de tâches d'arrière-plan : si l'utilisateur ferme l'écran, en arrière-plan la coroutine ne continuera pas à charger des données qui ne sont plus nécessaires.

WorkManager : tâches d'arrière-plan pour Android

WorkManager — une bibliothèque Android Jetpack pour exécuter des tâches d'arrière-plan qui doivent être accomplies même après un redémarrage de l'appareil ou la fermeture de l'application. Contrairement à Executors et aux coroutines, qui vivent dans le processus de l'application, WorkManager confie la tâche à un dispatcher système qui garantit l'exécution dans des conditions appropriées (disponibilité réseau, charge de la batterie, espace libre). WorkManager est adapté à la synchronisation de données, l'envoi de logs et la sauvegarde.

Une tâche dans WorkManager est une classe qui étend Worker (ou CoroutineWorker pour les coroutines). Worker.doWork() s'exécute sur un thread d'arrière-plan fourni par WorkManager. Le résultat est retourné via Result.success(), Result.retry() ou Result.failure(). Les tâches peuvent être chaînées : oneTimeWorkRequest.andThen(nextRequest).enqueue(). WorkManager choisit lui-même le moment d'exécution optimal en tenant compte des contraintes (Constraints).

kotlin
// WorkManager avec coroutines
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 {
        // Exécution sur Dispatchers.Default (par défaut)
        return withContext(Dispatchers.IO) {
            try {
                syncDataToServer()
                Result.success()
            } catch (e: Exception) {
                if (runAttemptCount < 3) Result.retry() else Result.failure()
            }
        }
    }

    private suspend fun syncDataToServer() {
        // Simulation de synchronisation
        delay(1000)
    }
}

// Lancement de tâche WorkManager avec contraintes
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 étend CoroutineWorker — une version de Worker prenant en charge les coroutines. doWork() s'exécute sur Dispatchers.Default, avec basculement sur IO pour les opérations réseau via withContext. Les Constraints garantissent que la synchronisation ne démarre qu'en présence de réseau et d'une charge batterie non inférieure à un niveau bas. BackoffCriteria avec EXPONENTIAL augmente l'intervalle entre les tentatives : 10, 20, 40 secondes.

PeriodicWorkRequest pour les tâches d'arrière-plan régulières

Pour les tâches régulières (synchronisation toutes les 15 minutes, envoi d'analyses toutes les heures), WorkManager fournit PeriodicWorkRequestBuilder. L'intervalle minimum est de 15 minutes. Contrairement à OneTimeWorkRequest, PeriodicWorkRequest ne garantit pas le respect exact de l'intervalle — le système peut regrouper plusieurs tâches périodiques pour économiser la batterie. Pour des intervalles précis, utilisez AlarmManager, mais tenez compte des restrictions d'Android 12+ sur les alarmes exactes.

Erreurs typiques avec les threads d'arrière-plan

Première erreur — créer un nouveau Thread pour chaque tâche. new Thread().start() crée un thread natif allouant ~1 Mo pour la pile. Pour 100 tâches parallèles, cela fait 100 Mo rien que pour les piles, plus les frais généraux de changement de contexte. Utilisez des pools de threads : Executors.newFixedThreadPool(n) (Android) ou DispatchQueue.global() (iOS) — ils réutilisent les threads, réduisant les frais généraux d'un ordre de grandeur.

Deuxième erreur — accéder à un état mutable depuis plusieurs threads d'arrière-plan sans synchronisation. Si deux threads d'arrière-plan écrivent simultanément dans le même ArrayList ou HashMap, des conditions de concurrence apparaissent : ConcurrentModificationException sous Android, corruption de données sous iOS. Solution : utilisez des collections thread-safe (ConcurrentHashMap, CopyOnWriteArrayList) ou sérialisez l'accès via une seule file d'attente (DispatchQueue serial).

Troisième erreur — des tâches d'arrière-plan sans gestion de cycle de vie. Lancer une coroutine dans une portée globale sans la lier au cycle de vie de l'Activity ou du ViewModel entraîne des fuites : la tâche continue de s'exécuter après la destruction de l'écran. Sous Android, utilisez lifecycleScope (Activity/Fragment) ou viewModelScope (ViewModel). Sous iOS, utilisez weak self dans les closures et annulez les tâches lors du deinit.

Questions fréquentes

Qu'est-ce qu'un Background Thread dans les applications mobiles ?

Background Thread — un thread sur lequel sont exécutées les opérations non liées à l'UI : requêtes réseau, lecture/écriture de fichiers, analyse JSON, calculs. Il libère le Main Thread du travail lourd, préservant la réactivité de l'interface. Sous iOS, les threads d'arrière-plan sont gérés via GCD (DispatchQueue.global) ; sous Android, via Executors ou Kotlin Coroutines (Dispatchers.IO, Dispatchers.Default).

Quelle est la différence entre Dispatchers.IO et Dispatchers.Default ?

Dispatchers.IO est conçu pour les opérations d'E/S bloquantes : lecture de fichiers, requêtes réseau, travail avec la BD. Il a un pool de 64 threads. Dispatchers.Default est pour les tâches intensives en CPU : tri, filtrage, traitement d'images. Son pool est égal au nombre de cœurs CPU. Utiliser Dispatchers.Default pour des opérations d'E/S peut bloquer tous les cœurs, et Dispatchers.IO pour des tâches CPU peut créer un nombre excessif de threads.

Comment basculer vers un thread d'arrière-plan sous iOS ?

DispatchQueue.global(qos: .background).async { } envoie un bloc dans la file d'attente globale d'arrière-plan. Après le travail d'arrière-plan, il faut revenir au thread principal via DispatchQueue.main.async { } pour mettre à jour l'UI. Pour les tâches séquentielles d'arrière-plan, utilisez OperationQueue avec maxConcurrentOperationCount = 1 ou DispatchQueue(label: "serial").

Combien de threads d'arrière-plan peut-on créer dans une application mobile ?

Le nombre recommandé de threads d'arrière-plan est égal au nombre de cœurs CPU plus 1 pour les tâches liées aux E/S. Sur un appareil moderne à 8 cœurs, cela donne 9 threads. Créer des centaines de threads entraîne thread starvation : l'OS passe plus de temps à changer de contexte qu'à exécuter des tâches. GCD sous iOS et Executors sous Android optimisent automatiquement le pool de threads pour l'appareil actuel.

Faut-il revenir au Main Thread après une coroutine ?

Dans Kotlin Coroutines, le retour au Main Thread se fait automatiquement si la coroutine a été lancée dans un scope Main (lifecycleScope.launch, viewModelScope.launch). La fonction withContext(Dispatchers.IO) suspend la coroutine sur un thread IO et, après la fin, la reprend automatiquement sur le dispatcher où elle a été lancée (généralement Main). Un appel explicite à DispatchQueue.main.async n'est pas nécessaire.

Résumé

  • Background Thread — un thread pour les opérations qui ne doivent pas s'exécuter sur le Main Thread : réseau, fichiers, analyse JSON, calculs
  • iOS : DispatchQueue.global(qos:) et OperationQueue sont les API principales pour les tâches d'arrière-plan avec support QoS
  • Android : Executors, HandlerThread, WorkManager pour Java ; Dispatchers.IO/Default + coroutines pour Kotlin
  • Coroutines avec withContext changent de thread sans callbacks et sans blocage (mécanisme suspend)
  • WorkManager garantit l'exécution des tâches d'arrière-plan même après redémarrage de l'appareil, en respectant les contraintes
  • Erreurs : création de nouveaux Threads au lieu d'utiliser un pool, conditions de concurrence sur état mutable, fuites par absence de liaison au cycle de vie
  • Résultat du thread d'arrière-plan est toujours retourné au Main Thread : via Dispatchers.Main (Android) ou DispatchQueue.main.async (iOS)

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi