withContext : ce que c'est, changement de contexte et travail dans les coroutines

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

withContext — est une fonction de changement de contexte d'exécution à l'intérieur d'une coroutine qui modifie temporairement le thread ou le dispatcher pour un bloc de code donné et retourne le résultat au contexte d'origine. Selon JetBrains, 2025, withContext est l'un des outils de coroutines les plus utilisés pour les requêtes réseau et les opérations disque. La fonction garantit qu'après la fin du bloc, la coroutine continue son exécution sur le dispatcher d'origine, évitant ainsi les erreurs accidentelles de sécurité des threads.

Points clés

  • withContext — une fonction suspendue qui modifie le CoroutineContext pour le bloc de code passé et retourne le résultat
  • Dispatchers.IO — argument typique pour basculer vers un thread d'arrière-plan pour les opérations réseau et disque
  • Dispatchers.Main — le contexte d'origine dans lequel withContext retourne automatiquement l'exécution après la fin du bloc
  • Appels séquentiels — withContext exécute le code séquentiellement, contrairement à launch et async, simplifiant le contrôle de l'ordre des opérations
  • Résultat Val — withContext retourne une valeur directement via return dans la dernière ligne du lambda, sans await ni join

Qu'est-ce que withContext dans Kotlin ?

withContext est une fonction suspendue du package kotlinx.coroutines qui exécute le bloc de code passé dans un CoroutineContext spécifié et retourne le résultat au contexte d'origine. La signature de la fonction est la suivante :

kotlin
suspend fun  withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T

Le paramètre context accepte n'importe quel CoroutineContext — le plus souvent l'un des Dispatchers.IO, Dispatchers.Default ou Dispatchers.Main standard. Le bloc s'exécute dans ce contexte, et le résultat est retourné là où withContext a été appelé.

Caractéristique clé : retour automatique

Après la fin du lambda, withContext bascule garantiment l'exécution vers le dispatcher d'origine. Cela signifie que le développeur n'a pas besoin d'appeler manuellement withContext(Dispatchers.Main) après une opération en arrière-plan — le retour se fait automatiquement. Ce comportement est documenté dans la spécification Kotlin Coroutines depuis la version 1.3.

Où withContext est utilisé

Le développement Android est le principal domaine d'utilisation de withContext. Un scénario typique : un ViewModel lance une coroutine sur le thread principal, à l'intérieur appelle withContext(Dispatchers.IO) pour une requête réseau, et le résultat après le retour automatique à Main est utilisé pour mettre à jour l'interface utilisateur. Cette approche est le fondement de l'architecture MVVM et est recommandée par Google dans le guide officiel des coroutines.

Comment fonctionne withContext : changement de dispatchers

Pour comprendre withContext, il faut comprendre le CoroutineContext et son composant clé — le dispatcher. Chaque coroutine possède un ensemble d'éléments de contexte, parmi lesquels le dispatcher détermine sur quel thread ou pool de threads le code s'exécute.

Dispatchers standard pour withContext

DispatcherObjectifTaille du pool
Dispatchers.MainThread principal de l'UI (Android, JavaFX, Swing)1 (thread principal)
Dispatchers.IOOpérations disque et réseau64 threads (limite croissante)
Dispatchers.DefaultCalculs intensifs CPUmax(2, nombre de cœurs)
Dispatchers.UnconfinedSans thread fixeillimité

Il est important de comprendre que withContext ne crée pas une nouvelle coroutine — il change uniquement le contexte pour la coroutine existante. C'est une différence clé avec launch et async, qui génèrent de nouvelles coroutines. L'implémentation interne de withContext est optimisée : si le contexte demandé correspond au contexte actuel, aucun changement ne se produit — la fonction s'exécute sur le même dispatcher.

Quand withContext NE change PAS de thread

Dispatchers.Main à l'intérieur de withContext(Dispatchers.Main) ne provoque pas de changement — Kotlin Coroutines reconnaît l'identité des contextes et ignore l'opération inutile. De même, withContext(Dispatchers.Default) à l'intérieur d'une coroutine déjà en cours d'exécution sur Default ne crée pas de surcharge. Cette optimisation est implémentée dans ContinuationInterceptor.

withContext vs launch et async : quand choisir quoi

Les débutants confondent souvent withContext avec launch et async, car les trois fonctions travaillent avec des coroutines et le contexte. Cependant, leur objectif est fondamentalement différent.

Comparaison des trois fonctions

CaractéristiquewithContextlaunchasync
Crée une nouvelle coroutineNonOuiOui
Retourne un résultatOui (T directement)Non (Job)Oui (Deferred<T>)
ExécutionSéquentielleParallèleParallèle
Attente du résultatAutomatiquejoin()await()
Cas d'utilisation typiqueChangement de dispatcherTirer-et-oublierCalculs parallèles

Règle de sélection

Si vous devez exécuter une opération sur un thread d'arrière-plan et obtenir un résultat — utilisez withContext. Si vous devez exécuter plusieurs opérations indépendantes en parallèle — utilisez async avec await. Si vous n'avez pas besoin du résultat (journalisation, écriture de cache) — utilisez launch. Google recommande withContext comme outil préféré pour la couche Repository dans l'architecture Android.

Exemples de code avec withContext

Examinons trois scénarios pratiques d'utilisation de withContext dans des applications Android avec Kotlin. Chaque exemple démontre une tâche spécifique et le modèle correct.

Exemple 1 : Requête réseau dans Repository

Un ViewModel appelle une méthode du référentiel depuis une coroutine sur Main. À l'intérieur, withContext(Dispatchers.IO) effectue une requête HTTP, et le résultat est retourné automatiquement :

kotlin
class UserRepository(
    private val api: UserApi
) {
    suspend fun getUser(id: String): User {
        return withContext(Dispatchers.IO) {
            api.fetchUser(id)
        }
    }
}

La coroutine dans le ViewModel appelle getUser comme n'importe quelle fonction suspendue normale — sans spécifier explicitement le dispatcher. withContext cache les détails du changement de thread.

Exemple 2 : Deux opérations d'arrière-plan séquentielles

Lorsque vous devez effectuer plusieurs opérations IO l'une après l'autre, withContext les combine en un seul bloc. C'est plus efficace que d'envelopper chaque opération dans un withContext séparé :

kotlin
suspend fun loadUserProfile(id: String): Profile {
    return withContext(Dispatchers.IO) {
        val user = api.fetchUser(id)
        val posts = api.fetchPosts(id)
        Profile(user, posts)
    }
}

Les deux opérations s'exécutent sur Dispatchers.IO, et le résultat Profile est créé et retourné sans changements de contexte inutiles. Si les opérations sont indépendantes, il est préférable d'utiliser async pour l'exécution parallèle.

Exemple 3 : Contexte mixte avec NonCancellable

Dans certains scénarios, vous devez exécuter du code qui ne peut pas être annulé — par exemple, sauvegarder l'état lors de la fermeture d'un écran. La combinaison de withContext + NonCancellable résout cette tâche :

kotlin
withContext(Dispatchers.IO + NonCancellable) {
    cache.saveState(state)
    analytics.logEvent("state_saved")
}

L'opérateur + combine deux éléments de contexte : le dispatcher IO et le drapeau NonCancellable. Le bloc s'exécute même si la coroutine parent a été annulée — utile pour les opérations de finalisation.

Ce qui se passe sous le capot : Continuation et optimisations

L'implémentation interne de withContext repose sur le mécanisme Continuation — l'abstraction centrale des coroutines Kotlin. Chaque point de suspension sauvegarde l'état d'exécution dans un objet Continuation, et withContext ne fait pas exception.

Comment withContext change le contexte au niveau bytecode

Le compilateur Kotlin traduit withContext en un appel à la méthode withContext de kotlinx.coroutines, qui crée en interne une nouvelle instance de DispatchedContinuation. Cet objet encapsule la Continuation d'origine et remplace son dispatcher. Si le nouveau dispatcher diffère du dispatcher actuel, l'exécution est suspendue, le bloc est envoyé au pool de threads correspondant, et après son achèvement — reprend avec le contexte d'origine.

Optimisation : fast-path lorsque les contextes correspondent

Lorsque withContext est appelé avec le même dispatcher que celui sur lequel la coroutine s'exécute déjà, Kotlin active le fast-path : le bloc s'exécute de manière synchrone, sans créer de DispatchedContinuation et sans l'envoyer au pool de threads. Cela rend withContext pratiquement gratuit pour les appels répétés avec le même contexte. Selon les benchmarks de JetBrains (kotlinx.coroutines 1.8), le fast-path se termine en moins de 0,1 µs.

Considérations sur les performances

Chaque appel à withContext avec un dispatcher différent crée un nouveau DispatchedContinuation et nécessite un changement de thread — cela prend de 1 à 5 µs selon la charge. Pour la plupart des applications, ce délai est imperceptible, mais à l'intérieur de boucles avec des milliers d'itérations, il vaut la peine d'agréger les opérations dans un seul bloc withContext.

Erreurs courantes lors de l'utilisation de withContext

Même les développeurs expérimentés commettent des erreurs en travaillant avec withContext. Examinons quatre problèmes courants et comment les éviter.

Erreur 1 : withContext imbriqués inutiles

Les développeurs enveloppent souvent chaque ligne dans un withContext séparé au lieu de combiner les opérations dans un seul bloc. Chaque appel supplémentaire avec un dispatcher différent crée une surcharge.

Correct : combiner les opérations IO séquentielles dans un seul withContext(Dispatchers.IO) { ... }. Si certaines opérations sont intensives en CPU — utilisez withContext(Dispatchers.Default) à l'intérieur du même bloc.

Erreur 2 : Utiliser withContext au lieu d'async pour les tâches parallèles

withContext exécute le code séquentiellement. Si deux requêtes réseau indépendantes sont enveloppées dans un seul withContext, elles s'exécuteront l'une après l'autre. Pour le parallélisme, utilisez async + await.

kotlin
// Séquentiel — lent
withContext(Dispatchers.IO) {
    val a = api.fetchA()
    val b = api.fetchB()
}

// Parallèle — rapide
coroutineScope {
    val a = async { api.fetchA() }
    val b = async { api.fetchB() }
    println("${a.await()} ${b.await()}")
}

Erreur 3 : Oublier NonCancellable pour les opérations critiques

Si une coroutine est annulée pendant withContext, le bloc sur Dispatchers.IO est également interrompu. Pour les opérations qui doivent être terminées à tout prix (écriture en base de données, envoi d'analyses), combinez withContext avec NonCancellable.

Erreur 4 : Mettre à jour l'état de l'UI à l'intérieur d'un bloc IO

Ne mettez jamais à jour les composants View à l'intérieur de withContext(Dispatchers.IO). withContext ne revient à Main qu'après la fin de tout le bloc. Placez les mises à jour de l'interface utilisateur après l'accolade fermante de withContext — alors la coroutine sera déjà sur le thread principal.

Foire aux questions

Quelle est la différence entre withContext et runBlocking ?

withContext est une fonction suspendue qui ne bloque pas le thread, mais change le contexte à l'intérieur d'une coroutine existante. runBlocking est un pont entre les coroutines et le code normal qui bloque le thread actuel jusqu'à la fin. withContext est sûr pour le thread de l'UI, runBlocking ne l'est pas.

Peut-on utiliser withContext sans suspend ?

Non, withContext est une fonction suspend, donc elle ne peut être appelée que depuis une autre fonction suspend ou depuis une coroutine (launch/async). Depuis une fonction normale, withContext ne peut pas être appelé — pour cela, vous avez besoin de runBlocking ou CoroutineScope.

Que se passe-t-il si on passe le même dispatcher à withContext ?

Kotlin active le fast-path — le bloc s'exécute de manière synchrone sur le même thread sans changement. La surcharge est inférieure à 0,1 µs. Ce n'est pas une erreur, mais un tel appel est redondant — il est préférable d'exécuter le code simplement sans withContext.

Comment withContext fonctionne-t-il avec les exceptions ?

Les exceptions à l'intérieur de withContext se propagent de la même manière que dans le code normal — via try-catch. Si le bloc lève une exception, elle se propage à la coroutine parent et l'annule si elle n'est pas traitée. Utilisez try-catch à l'intérieur de withContext ou autour de lui.

withContext crée-t-il une nouvelle coroutine ou non ?

Non, withContext ne crée pas une nouvelle coroutine. Il utilise la coroutine existante mais change temporairement son contexte. Cela le distingue de launch et async, qui génèrent des coroutines filles. Ce comportement est confirmé par le code source de kotlinx.coroutines.

Résumé

  • withContext — une fonction suspend pour changer le CoroutineContext à l'intérieur d'une coroutine existante avec retour automatique au contexte d'origine
  • Dispatchers.IO — le dispatcher principal pour les requêtes réseau et les opérations disque à l'intérieur de withContext
  • Fast-path — une optimisation Kotlin où withContext avec le même dispatcher s'exécute de manière synchrone sans surcharge
  • Tâches parallèles nécessitent async/await, pas withContext — withContext exécute le code séquentiellement
  • NonCancellable — un drapeau pour les opérations critiques à l'intérieur de withContext qui ne doivent pas être interrompues lors de l'annulation de la coroutine
  • Couche Repository — l'emplacement recommandé pour withContext dans l'architecture Android selon les directives de Google
  • Continuation — le mécanisme sous-jacent au changement de contexte dans withContext au niveau bytecode de Kotlin

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