CoroutineScope — définition, cycle de vie et travail dans les coroutines

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

CoroutineScope est une interface Kotlin qui définit le cycle de vie d'une coroutine et fournit un contexte pour lancer de nouvelles coroutines. Selon la documentation Kotlin, 2025, chaque instance CoroutineScope contient un CoroutineContext et gère toutes les coroutines lancées à l'intérieur. Lorsque le scope est annulé (cancel), toutes les coroutines filles sont automatiquement annulées, ce qui évite les fuites mémoire.

Points clés

  • CoroutineScope — une interface avec un seul champ CoroutineContext, définissant le cycle de vie des coroutines
  • Job — un élément de contexte responsable de l'annulation : annuler le scope annule toutes les coroutines filles
  • Concurrence structurée — le principe selon lequel les coroutines filles sont liées au scope parent
  • GlobalScope — un scope pour toute l'application qui n'est pas recommandé en raison du risque de fuites mémoire
  • supervisorScope — un scope spécial où l'annulation d'une coroutine fille n'annule pas les autres

Qu'est-ce que CoroutineScope dans Kotlin ?

CoroutineScope est une interface fondamentale de la bibliothèque kotlinx.coroutines qui sert de conteneur pour les coroutines. Elle définit les limites du cycle de vie des coroutines : lorsque le scope se termine, toutes les coroutines à l'intérieur sont automatiquement annulées.

kotlin
public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

L'interface ne contient qu'un seul champ — coroutineContext. À travers lui, le scope fournit un dispatcher (Dispatcher), un job (Job), un gestionnaire d'exceptions et d'autres éléments de contexte à toutes les coroutines lancées à l'intérieur.

Rôle dans la bibliothèque kotlinx.coroutines

Toutes les fonctions de lancement de coroutines — launch, async, runBlocking — sont des fonctions d'extension sur CoroutineScope. Cela signifie qu'elles ne peuvent être appelées que lorsqu'un objet scope est disponible. Cette conception garantit que chaque coroutine a un parent et un cycle de vie clairement définis.

Où CoroutineScope est utilisé

Dans Android, chaque composant architectural a son propre scope : viewModelScope pour ViewModel, lifecycleScope pour Activity/Fragment. Dans les applications serveur, un scope peut être lié à une requête HTTP ou à un pool de connexions de base de données.

Comment fonctionne CoroutineScope : Job et concurrence structurée

Comprendre le fonctionnement interne de CoroutineScope nécessite une familiarité avec le concept de Job et le principe de concurrence structurée.

Job — la tâche de la coroutine

Chaque coroutine au lancement retourne un objet Job (ou Deferred pour async). Job représente une tâche avec un cycle de vie fini : New, Active, Completing, Completed, Cancelling, Cancelled. Les objets Job forment une structure arborescente :

  • Job parent — le scope dans lequel la coroutine a été lancée
  • Job fils — chaque coroutine lancée via launch/async
  • Annulation du parent → annulation de tous les fils
  • Exception dans un fils → annulation du parent (sauf dans supervisorScope)

Le principe de concurrence structurée

Concurrence structurée est un principe architectural clé de Kotlin Coroutines, où le cycle de vie de la coroutine est lié au cycle de vie de son scope. Cela contraste avec le modèle « tirer et oublier », où une coroutine continue de vivre après la fin du scope. Avantages de la concurrence structurée :

  • Cycle de vie prévisible — lorsque le scope se termine, toutes les coroutines sont arrêtées
  • Gestion automatique des erreurs — une exception dans toute coroutine fille se propage au scope
  • Pas de fuites mémoire — aucune coroutine ne reste en cours d'exécution après la fin du scope
  • Hiérarchie claire — le code reflète la structure logique des opérations parallèles

Cycle de vie de CoroutineScope

Lorsque scope.cancel() est appelé, le Job du scope passe à l'état Cancelled, ce qui annule récursivement tous les Jobs fils. Après l'annulation, le scope ne peut être réutilisé qu'en créant une nouvelle instance de CoroutineScope.

Création et configuration de CoroutineScope

Vous pouvez créer un CoroutineScope via une fonction d'usine ou en implémentant l'interface dans votre classe. Examinons les deux approches.

Fonction d'usine CoroutineScope()

kotlin
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())

scope.launch {
    println("Running on ${Thread.currentThread().name}")
}

La fonction d'usine prend un CoroutineContext et crée un scope avec le contexte spécifié. L'exemple utilise Dispatchers.Default pour les tâches intensives en CPU et SupervisorJob, qui isole les exceptions entre coroutines filles.

Implémentation de l'interface via composition

kotlin
class MyRepository {
    private val scope = CoroutineScope(Dispatchers.IO + Job())

    suspend fun fetchData(): Data = scope.async {
        api.getData()
    }.await()

    fun cleanup() {
        scope.cancel()
    }
}

Nous stockons le scope comme un champ de classe et appelons manuellement cleanup pour l'annuler. Cette approche convient aux composants avec un cycle de vie géré — par exemple, les repositories ou les gestionnaires.

Implémentation via délégation

Kotlin permet de déléguer l'implémentation de CoroutineScope via le mot-clé by :

kotlin
class DataLoader : CoroutineScope by CoroutineScope(Dispatchers.IO) {
    fun load() {
        launch {
            // coroutine runs in DataLoader scope
        }
    }
}

Cette approche est pratique lorsque la classe elle-même est un scope et souhaite fournir des méthodes de lancement de coroutines. Cependant, attention : la classe hérite de toutes les méthodes de CoroutineScope, y compris cancel, ce qui peut briser l'encapsulation.

GlobalScope vs CoroutineScope personnalisé

GlobalScope est un singleton CoroutineScope pour toute l'application. Son utilisation en code de production n'est officiellement pas recommandée.

Problèmes avec GlobalScope

  • Absence de concurrence structurée — les coroutines dans GlobalScope ne sont pas liées au cycle de vie du composant
  • Fuites mémoire — une coroutine peut continuer à s'exécuter après la fermeture de l'Activity/Fragment
  • Tests difficiles — GlobalScope ne peut pas être remplacé dans les tests
  • Consommation non contrôlée des ressources — de nombreuses coroutines peuvent s'exécuter plus longtemps que prévu

Quand GlobalScope est justifié

JetBrains autorise GlobalScope uniquement dans des scénarios rares : processus d'arrière-plan au niveau de l'application qui doivent vivre même après la fermeture de toutes les Activities (par exemple, synchronisation de données, analytique). Mais même dans ces cas, il est préférable de créer votre propre scope avec CoroutineScope(SupervisorJob()).

Recommandation

Utilisez toujours un CoroutineScope personnalisé avec une gestion explicite du cycle de vie. Dans Android, ce sont viewModelScope et lifecycleScope. Dans les applications serveur, créez un scope pour chaque requête ou pool de connexions.

coroutineScope vs supervisorScope : quelle est la différence

Les deux fonctions sont des fonctions suspend qui créent un scope temporaire pour les tâches parallèles, mais leur comportement face aux exceptions diffère fondamentalement.

CaractéristiquecoroutineScopesupervisorScope
Comportement en cas d'erreurUne exception dans une coroutine fille annule toutes les autresUne exception dans une coroutine fille n'annule PAS les autres
Propagation d'erreurOui, la première exception est propagée vers l'extérieurOui, la première exception est propagée vers l'extérieur
Job par défautJob() — les fils sont liés au parentSupervisorJob() — les fils ne dépendent pas les uns des autres
Cas d'utilisation typiqueOpération atomique en plusieurs étapesTâches parallèles indépendantes (chargements UI)

Quand choisir coroutineScope

Utilisez coroutineScope lorsque plusieurs opérations parallèles forment une seule opération atomique. Par exemple, charger des données depuis trois serveurs : si une requête échoue, les autres n'ont pas de sens.

kotlin
suspend fun loadProductPage(): ProductPage = coroutineScope {
    val product = async { api.getProduct() }
    val reviews = async { api.getReviews() }
    ProductPage(product.await(), reviews.await())
}

Si getProduct ou getReviews lève une exception — les deux coroutines sont annulées et l'exception est propagée au code appelant.

Quand choisir supervisorScope

Utilisez supervisorScope lorsque les opérations parallèles ne dépendent pas les unes des autres. Par exemple, charger les données de profil dans plusieurs sections indépendantes : si la section des recommandations échoue, l'en-tête du profil et la liste d'amis doivent s'afficher.

Erreurs courantes avec CoroutineScope

Examinons les erreurs les plus fréquentes des développeurs lors de l'utilisation de CoroutineScope dans Kotlin.

Erreur 1 : Oublier d'annuler le scope

Le scénario le plus courant de fuite de coroutine est la création d'un scope sans appeler cancel à la fin du composant. Si le scope n'est pas annulé, les coroutines continuent de s'exécuter, maintenant des références aux objets. Dans Android, utilisez viewModelScope ou lifecycleScope, qui sont annulés automatiquement.

Erreur 2 : Utiliser GlobalScope dans Activity ou Fragment

GlobalScope ignore le cycle de vie des composants Android. Une coroutine lancée dans GlobalScope après la fermeture d'une Activity continuera à s'exécuter et tentera de mettre à jour l'UI — ce qui provoquera un crash. Utilisez toujours lifecycleScope pour les composants UI.

Erreur 3 : Réutiliser un scope annulé

Après avoir appelé cancel(), le scope ne peut pas être réutilisé — toutes les coroutines à l'intérieur sont déjà terminées. Créez une nouvelle instance de CoroutineScope via la fonction d'usine. Job() ne prend pas en charge la réactivation.

Erreur 4 : Délégation incorrecte de l'interface CoroutineScope

En déléguant avec by, la classe obtient une méthode cancel() publique qui peut être appelée de n'importe où, brisant l'encapsulation. Stockez le scope comme un champ privé plutôt que de déléguer l'interface.

Questions fréquentes

Quelle est la différence entre CoroutineScope et CoroutineContext ?

CoroutineScope est une interface qui possède un CoroutineContext et est responsable du cycle de vie des coroutines. CoroutineContext est un ensemble d'éléments (dispatcher, job, gestionnaire d'erreurs) qui définit « comment » une coroutine s'exécute. Une différence : le scope crée des coroutines, tandis que le contexte contrôle leur comportement.

Peut-on créer un CoroutineScope avec SupervisorJob ?

Oui, c'est un modèle standard : CoroutineScope(Dispatchers.IO + SupervisorJob()). SupervisorJob empêche l'annulation en cascade des coroutines filles lorsque l'une d'elles lève une exception. C'est utile pour les tâches parallèles indépendantes où une erreur dans l'une ne doit pas arrêter les autres.

Combien de coroutines un CoroutineScope peut-il contenir ?

Il n'y a pas de limite sur le nombre de coroutines dans un scope — elles sont seulement limitées par la mémoire disponible et les paramètres du dispatcher. La limite pratique est typiquement de milliers de coroutines actives dans un seul scope. Cependant, un grand nombre de coroutines peut indiquer des problèmes architecturaux.

Comment tester du code avec CoroutineScope ?

La bonne méthode est de passer le scope à la classe via le constructeur ou d'utiliser runBlockingTest / runTest de kotlinx-coroutines-test. Dans les tests, vous pouvez remplacer le scope par TestCoroutineDispatcher et contrôler l'exécution des coroutines manuellement.

Une coroutine peut-elle avoir son propre scope ?

Non, un scope est un conteneur externe pour une coroutine. La coroutine elle-même n'est pas un scope. Cependant, à l'intérieur d'une coroutine, vous pouvez créer un nouveau scope via coroutineScope ou supervisorScope pour lancer des coroutines filles en parallèle.

Résumé

  • CoroutineScope — une interface avec un champ coroutineContext, définissant le cycle de vie des coroutines lancées à l'intérieur
  • Concurrence structurée — annuler le scope annule automatiquement toutes les coroutines filles, évitant les fuites mémoire
  • Job et SupervisorJob — deux modes de gestion des erreurs : annulation en cascade (Job) et erreurs isolées (SupervisorJob)
  • GlobalScope — non recommandé pour la production en raison de l'absence de liaison au cycle de vie
  • coroutineScope vs supervisorScope — opérations parallèles atomiques vs tâches parallèles indépendantes
  • viewModelScope et lifecycleScope — scopes prêts pour Android, annulés automatiquement à la fin du composant
  • Fonction d'usine — la façon préférée de créer un scope via CoroutineContext + appel explicite à cancel

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