Dispatchers — concepts clés, types de dispatchers et leur fonctionnement

Auteur : IT Sectr Publié le : 2026-06-22 Temps de lecture : 9 min

Dispatchers dans Kotlin Coroutines sont des composants de CoroutineContext qui déterminent les threads d'exécution des coroutines : Main (thread UI), IO (réseau et disque), Default (tâches intensives CPU) et Unconfined (thread actuel). Chaque dispatcher gère un pool de threads spécialisé optimisé pour un type de travail spécifique. Selon le guide JetBrains, 2024, le choix du bon dispatcher est essentiel pour les performances et la stabilité de l'application.

Points Clés

  • Dispatchers — dispatchers de coroutines prédéfinis qui gèrent la répartition des tâches entre les threads
  • Dispatchers.Main — thread UI Android, pour mettre à jour l'interface et travailler avec View
  • Dispatchers.IO — pool pour les requêtes réseau, lecture/écriture de fichiers et BD (jusqu'à 64 threads)
  • Dispatchers.Default — pool pour les tâches intensives CPU basé sur le nombre de cœurs du processeur
  • Dispatchers.Unconfined — hérite du thread actuel, sans commutation

Que sont les Dispatchers ?

Dispatchers sont des implémentations de l'interface CoroutineDispatcher, qui sont des éléments de CoroutineContext. Ils déterminent sur quel thread ou pool de threads la coroutine sera exécutée. Lors de la création d'une coroutine via launch ou async, le dispatcher peut être passé comme premier paramètre : launch(Dispatchers.IO) { ... }. Si aucun dispatcher n'est spécifié, il est hérité du CoroutineScope externe.

Kotlin fournit quatre dispatchers intégrés : Main, IO, Default, Unconfined. Chaque dispatcher utilise son propre pool de threads optimisé pour un type d'opération spécifique. Choisir le bon dispatcher détermine les performances de l'application : un mauvais choix entraîne des ralentissements de l'UI, des cœurs CPU inactifs ou une utilisation inefficace des threads.

DispatcherPool de threadsMax threadsUtilisation
Dispatchers.MainUn (UI)1Mise à jour UI, LiveData, View
Dispatchers.IOPool IO64 (limitedParallelism)Réseau, fichiers, BD
Dispatchers.DefaultPool CPUN cœursTri, analyse, calculs
Dispatchers.UnconfinedThread actuelN/AOpérations intermédiaires, tests

Dispatchers.Main : Thread UI

Dispatchers.Main est le dispatcher qui exécute les coroutines sur le thread principal Android. Il est conçu pour les opérations liées à l'interface : mise à jour de TextView, appel de notifyDataSetChanged, travail avec LiveData et StateFlow. Dans Android, ce dispatcher est implémenté via Handler (Looper.getMainLooper()).

kotlin
// Correct switch to Main for UI updates
viewModelScope.launch(Dispatchers.IO) {
    val data = repository.fetchData()
    withContext(Dispatchers.Main) {
        _uiState.value = data
    }
}

Si une coroutine est déjà sur le dispatcher Main, un withContext(Dispatchers.Main) supplémentaire ne crée pas de surcharge — le dispatcher vérifie le thread actuel et ignore la commutation. withContext est la méthode préférée pour basculer entre les dispatchers.

Dispatchers.IO : Opérations réseau et disque

Dispatchers.IO est un dispatcher optimisé pour les opérations d'E/S : requêtes HTTP (Ktor, OkHttp), lecture et écriture de fichiers, travail avec Room ou SQLDelight. Il utilise un pool de 64 threads par défaut, évolutif sous charge. Chaque nouvelle requête d'E/S peut créer un thread supplémentaire jusqu'à atteindre la limite.

Limitation du parallélisme

Pour contrôler le nombre d'opérations d'E/S simultanées, utilisez limitedParallelism(). Cette fonction crée un nouveau dispatcher avec une limite sur le nombre de threads parallèles, empêchant l'épuisement du pool lors des opérations de masse.

kotlin
val limitedIo = Dispatchers.IO.limitedParallelism(4)

// Load 100 files with limit of 4 concurrent operations
coroutineScope {
    val files = (1..100).map { index ->
        async(limitedIo) {
            downloadFile("file_$index")
        }
    }
    files.awaitAll()
}

Utilisez le dispatcher IO pour toutes les opérations où la coroutine passe du temps à attendre (I/O-bound). Les tâches intensives CPU sur le dispatcher IO sont inefficaces — elles occupent des threads destinés aux E/S, réduisant le débit du système.

Dispatchers.Default : Tâches intensives CPU

Dispatchers.Default est le dispatcher pour les opérations de calcul qui chargent le processeur : tri, filtrage, analyse JSON (Moshi, Kotlinx Serialization), traitement d'images, calculs. La taille du pool est égale au nombre de cœurs du processeur (mais pas moins de 2). Cela garantit une utilisation maximale du CPU sans changement de contexte.

kotlin
suspend fun processData(input: List<RawRecord>): List<ProcessedRecord> {
    return withContext(Dispatchers.Default) {
        input
            .parallelStream()
            .map { transform(it) }
            .toList()
    }
}

N'utilisez pas Dispatchers.Default pour les opérations d'E/S — cela bloquera les threads du pool CPU qui pourraient traiter des tâches computationnelles. La séparation entre IO et Default permet une utilisation optimale des ressources système : les threads IO attendent les E/S, les threads CPU sont constamment occupés par des calculs.

Dispatchers.Unconfined : Thread actuel

Dispatchers.Unconfined est un dispatcher spécial qui ne lie pas une coroutine à un pool. La coroutine commence son exécution dans le thread où launch/async a été appelé, et après la suspension, reprend dans le thread qui a appelé resume. Ce comportement convient aux opérations intermédiaires qui ne nécessitent pas de contexte fixe.

kotlin
fun main() = runBlocking {
    launch(Dispatchers.Unconfined) {
        println("Before delay: ${Thread.currentThread().getName()}")
        delay(500L)
        println("After delay: ${Thread.currentThread().getName()}")
    }
}

Dans le code de production, Dispatchers.Unconfined est rarement utilisé. Cas d'utilisation principaux : transformations légères avant de passer des données à un autre dispatcher et tests. Pour les charges de production, utilisez des dispatchers explicites — Unconfined est imprévisible car le thread d'exécution dépend de l'implémentation de resume.

Comment choisir un dispatcher

La sélection du dispatcher dépend du type de tâche : opérations UI → Main, I/O-bound → IO, CPU-bound → Default, intermédiaires → hériter du scope. Pour Android, il est recommandé de lancer une coroutine sur le dispatcher où le travail principal est effectué, et de basculer vers Main via withContext avant de mettre à jour l'UI.

  • N'exécutez pas d'opérations d'E/S sur le dispatcher Main — cela bloque l'UI
  • N'exécutez pas de tâches intensives CPU sur le dispatcher IO — cela consomme inefficacement les threads du pool IO
  • Utilisez limitedParallelism pour contrôler la concurrence lors des opérations d'E/S de masse
  • Basculez de dispatcher avec withContext, pas en créant un nouveau scope
  • Héritez du dispatcher du scope si la coroutine ne nécessite pas de pool spécifique

Pour les scénarios complexes, combinez les dispatchers avec l'opérateur + : Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler. Cela crée un CoroutineContext avec un dispatcher spécifié, une gestion des erreurs et une hiérarchie Job isolée.

Foire aux questions

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

Dispatchers.IO utilise un pool allant jusqu'à 64 threads pour les opérations I/O-bound (attente d'E/S), tandis que Dispatchers.Default utilise un pool basé sur le nombre de cœurs CPU pour les tâches computationnelles. Lorsque les threads sont rares, les deux pools peuvent partager des threads entre eux.

Puis-je créer mon propre dispatcher dans Kotlin Coroutines ?

Oui, utilisez newSingleThreadContext() pour un thread unique ou newFixedThreadPoolContext() pour un pool fixe. Pour la production, utilisez limitedParallelism() basé sur les dispatchers existants — c'est plus efficace que de créer de nouveaux pools.

Que se passe-t-il lorsque Dispatchers.Main est appelé depuis un thread d'arrière-plan ?

Si Dispatchers.Main n'est pas disponible (par exemple, dans un test JUnit ou un service d'arrière-plan), une IllegalStateException est levée. Utilisez TestCoroutineDispatcher pour les tests et Dispatchers.IO ou Default pour les services d'arrière-plan.

Comment limiter le nombre d'opérations d'E/S parallèles ?

Utilisez Dispatchers.IO.limitedParallelism(N), où N est le nombre maximum de threads parallèles. Cela évite l'épuisement du pool lors des requêtes de masse et fournit un parallélisme contrôlé.

Quand utiliser Dispatchers.Unconfined ?

Dispatchers.Unconfined convient aux opérations intermédiaires : transformations légères de données avant de passer à un autre dispatcher, scénarios de test. Dans le code Android de production, il n'est pas recommandé en raison du thread d'exécution indéfini après la suspension.

Résumé

  • Dispatchers — composants de CoroutineContext qui déterminent les threads d'exécution des coroutines
  • Dispatchers.Main — pour les opérations UI Android, un thread principal
  • Dispatchers.IO — pour les opérations réseau et disque, pool jusqu'à 64 threads
  • Dispatchers.Default — pour les calculs intensifs CPU, pool basé sur le nombre de cœurs
  • Dispatchers.Unconfined — sans liaison de thread, pour les opérations intermédiaires
  • withContext — le mécanisme principal pour basculer entre les dispatchers dans une coroutine
  • Choisir le mauvais dispatcher entraîne des ralentissements de l'UI ou une utilisation inefficace des ressources

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