Contactez-nous

Fonctions `suspend`

Apprenez le rôle clé du mot-clé `suspend` en Kotlin coroutines. Découvrez comment déclarer et utiliser les fonctions suspendables pour la programmation asynchrone.

Introduction : Le marqueur de la suspension

Au coeur du mécanisme des coroutines en Kotlin se trouve un mot-clé spécial : `suspend`. Ce mot-clé est un modificateur que l'on applique à la déclaration d'une fonction. Une fonction marquée avec `suspend` est appelée une fonction suspendable ou suspending function.

La présence de `suspend` signale quelque chose de fondamental sur le comportement potentiel de cette fonction : elle peut suspendre son exécution à certains points sans bloquer le thread courant, pour la reprendre ultérieurement. C'est ce mécanisme de suspension qui permet d'écrire du code asynchrone de manière séquentielle et non bloquante.

Comprendre le rôle et les règles d'utilisation des fonctions `suspend` est la première étape essentielle pour travailler efficacement avec les coroutines.

Syntaxe : Déclarer une fonction suspendable

La syntaxe pour déclarer une fonction suspendable est très simple : il suffit d'ajouter le mot-clé `suspend` avant le mot-clé `fun`.

// Déclaration d'une fonction suspendable simple
suspend fun doSomethingLongRunning() {
    println("Tâche longue démarrée...")
    // Simule un travail qui prend du temps (ex: appel réseau)
    kotlinx.coroutines.delay(1000L) // delay() est elle-même une fonction suspend
    println("Tâche longue terminée.")
}

// Fonction suspendable retournant une valeur
suspend fun fetchDataFromServer(url: String): String {
    println("Récupération depuis $url...")
    kotlinx.coroutines.delay(500L) // Simule l'attente réseau
    return "{\"data\": \"Données de $url\"}"
}

// Fonction suspendable avec paramètres
suspend fun saveUserData(user: String, data: String) {
    println("Sauvegarde des données pour $user...")
    kotlinx.coroutines.delay(200L) // Simule l'accès disque
    println("Données sauvegardées.")
}

La présence de `suspend` ne change rien d'autre à la signature de la fonction (paramètres, type de retour). Elle modifie uniquement ses capacités d'invocation et son comportement interne potentiel.

Règles d'invocation : Où peut-on appeler une fonction `suspend` ?

Une contrainte fondamentale s'applique aux fonctions `suspend` : elles ne peuvent être appelées que depuis un contexte spécifique qui "comprend" la suspension. Concrètement, une fonction `suspend` ne peut être appelée que :

1. Depuis une autre fonction `suspend`.

2. Depuis l'intérieur d'une coroutine démarrée via un constructeur de coroutine (comme `launch`, `async`, `runBlocking`, etc.). Ces constructeurs créent la portée et le contexte nécessaires pour gérer la suspension.

Essayer d'appeler une fonction `suspend` directement depuis une fonction ordinaire (non-`suspend`) entraînera une erreur de compilation.

suspend fun taskA() { println("Tâche A") }
suspend fun taskB() {
    println("Début Tâche B")
    taskA() // OK: Appel de suspend depuis suspend
    println("Fin Tâche B")
}

fun regularFunction() {
    println("Fonction régulière")
    // taskB() // Erreur de compilation: Suspend function 'taskB' should be called only from a coroutine or another suspend function
}

// Pour appeler taskB, on doit utiliser un constructeur de coroutine
fun main() = kotlinx.coroutines.runBlocking { // runBlocking est un constructeur qui crée une coroutine et bloque le thread principal
    println("Début Main")
    regularFunction()
    taskB() // OK: Appel de suspend depuis une coroutine (créée par runBlocking)
    println("Fin Main")
}

// Définitions (pour l'exemple)
suspend fun taskA() { println("Tâche A"); kotlinx.coroutines.delay(100) }
suspend fun taskB() {
    println("Début Tâche B")
    taskA()
    println("Fin Tâche B")
    kotlinx.coroutines.delay(100)
}
fun regularFunction() { println("Fonction régulière") }

Cette règle garantit que la suspension est toujours gérée dans un contexte approprié.

Comportement : Que fait `suspend` réellement ?

Il est crucial de comprendre que le mot-clé `suspend` lui-même ne rend pas automatiquement une fonction asynchrone ou ne la fait pas s'exécuter sur un autre thread. Une fonction `suspend` peut très bien ne contenir que du code synchrone classique.

Le véritable pouvoir de `suspend` est qu'il permet à la fonction d'appeler d'autres fonctions `suspend`. C'est lors de l'appel à ces autres fonctions `suspend` (qui représentent généralement les opérations asynchrones réelles comme `delay`, un appel réseau, un accès disque) que la suspension peut se produire.

Lorsqu'une coroutine atteint un appel à une fonction `suspend` (un "point de suspension"), son état est sauvegardé, et le thread sur lequel elle s'exécutait est libéré pour faire autre chose. Une fois l'opération asynchrone sous-jacente terminée, la coroutine est programmée pour reprendre son exécution (potentiellement sur un autre thread) juste après le point de suspension, avec son état restauré.

Une fonction marquée `suspend` mais qui n'appelle aucune autre fonction `suspend` à l'intérieur se comportera exactement comme une fonction régulière, s'exécutant de manière synchrone et bloquante sur le thread appelant.

// Fonction suspend qui ne suspend jamais réellement
suspend fun synchronousSuspend() {
    println("Début sync suspend")
    // Pas d'appel à une autre fonction suspend ici (ex: pas de delay)
    Thread.sleep(500) // Ceci BLOQUE le thread !
    println("Fin sync suspend")
}

// Fonction suspend qui suspend réellement
suspend fun trulyAsynchronous() {
    println("Début async suspend")
    kotlinx.coroutines.delay(500) // Ceci SUSPEND la coroutine, sans bloquer le thread
    println("Fin async suspend")
}

fun main() = kotlinx.coroutines.runBlocking {
    println("Appel sync suspend...")
    synchronousSuspend()
    println("Appel async suspend...")
    trulyAsynchronous()
    println("Terminé")
}

La magie se produit lorsque vous appelez des fonctions `suspend` fournies par les bibliothèques de coroutines (comme `delay`) ou par des bibliothèques tierces adaptées aux coroutines (comme Ktor pour le réseau, Room pour les bases de données Android) qui implémentent la logique de suspension réelle.

Les briques de l'asynchronisme séquentiel

Les fonctions `suspend` sont les briques élémentaires qui permettent d'écrire du code asynchrone de manière séquentielle. En marquant une fonction avec `suspend`, vous lui donnez la capacité d'orchestrer des opérations asynchrones sans la complexité des callbacks ou des threads manuels. Le compilateur transforme ce code séquentiel en une machine à états en coulisses pour gérer la suspension et la reprise.

Lorsque vous voyez `suspend fun`, cela signifie que cette fonction peut potentiellement prendre du temps ou effectuer une opération non bloquante, et qu'elle doit être appelée depuis un contexte de coroutine.

Récapitulatif : le rôle de `suspend`

Le mot-clé `suspend` est fondamental pour les coroutines :

  • Il marque une fonction comme étant suspendable.
  • Une fonction `suspend` peut suspendre son exécution (lorsqu'elle appelle une autre fonction `suspend`) sans bloquer le thread.
  • Elle ne peut être appelée que depuis une autre fonction `suspend` ou un constructeur de coroutine.
  • Elle ne rend pas une fonction asynchrone par elle-même ; elle lui permet d'appeler d'autres fonctions suspendables qui gèrent l'asynchronisme.
  • Permet d'écrire du code asynchrone de manière séquentielle et lisible.

Maîtriser le concept des fonctions `suspend` est la clé pour comprendre et utiliser efficacement les coroutines Kotlin.