⏸️ Fonctions suspend
📖 Qu'est-ce que suspend ?
Les fonctions suspend sont des fonctions spéciales qui peuvent suspendre une coroutine. Elles sont efficaces car elles permettent d'attendre sans bloquer le thread !
💡 Concepts de base
Le mot-clé suspend
// Une fonction suspend peut appeler d'autres fonctions suspend
suspend fun doSomething() {
delay(1000) // fonction suspend
println("Terminé !")
}
fun main() = runBlocking {
doSomething() // Appel dans une coroutine
}
Fonction normale vs fonction suspend
// ❌ Impossible d'appeler une fonction suspend depuis une fonction normale
fun normalFunction() {
// delay(1000) // Erreur de compilation !
}
// ✅ Possible dans une fonction suspend
suspend fun suspendFunction() {
delay(1000) // OK !
}
🎯 Exemples pratiques
Requête réseau
data class User(val id: String, val name: String)
suspend fun fetchUser(userId: String): User {
delay(1000) // Simulation d'un délai réseau
return User(userId, "홍길동")
}
fun main() = runBlocking {
println("Début de la requête")
val user = fetchUser("user123")
println("Utilisateur reçu : ${user.name}")
}
Exécution séquentielle
suspend fun prepareFood(): String {
delay(1000)
return "음식"
}
suspend fun setTable(): String {
delay(500)
return "테이블"
}
suspend fun prepareMeal() {
println("Début de la préparation")
val food = prepareFood()
println("$food préparé")
val table = setTable()
println("$table préparé")
println("Toute la préparation est terminée !")
}
fun main() = runBlocking {
val time = measureTimeMillis {
prepareMeal()
}
println("Temps écoulé : ${time}ms") // ~1500ms
}
Exécution parallèle
suspend fun prepareMealParallel() = coroutineScope {
println("Début de la préparation")
val foodDeferred = async { prepareFood() }
val tableDeferred = async { setTable() }
val food = foodDeferred.await()
val table = tableDeferred.await()
println("$food, $table préparés")
}
fun main() = runBlocking {
val time = measureTimeMillis {
prepareMealParallel()
}
println("Temps écoulé : ${time}ms") // ~1000ms (parallèle !)
}
🔧 withContext
Changement de dispatcher
suspend fun loadData(): String {
return withContext(Dispatchers.IO) {
// Exécution sur le thread IO
delay(1000)
"데이터"
}
}
fun main() = runBlocking {
println("Thread principal : ${Thread.currentThread().name}")
val data = loadData()
println("Retour au principal : ${Thread.currentThread().name}")
println("Données : $data")
}
Plusieurs contextes
suspend fun processData() {
// Opération IO
val data = withContext(Dispatchers.IO) {
readFromFile()
}
// Calcul intensif CPU
val processed = withContext(Dispatchers.Default) {
heavyComputation(data)
}
// Thread principal (mise à jour UI, etc.)
withContext(Dispatchers.Main) {
updateUI(processed)
}
}
suspend fun readFromFile(): String {
delay(500)
return "파일 내용"
}
suspend fun heavyComputation(data: String): String {
delay(1000)
return data.uppercase()
}
suspend fun updateUI(data: String) {
println("Mise à jour UI : $data")
}
🎨 Patterns avancés
Attente conditionnelle
suspend fun fetchDataIfNeeded(cache: String?): String {
return cache ?: fetchFromNetwork()
}
suspend fun fetchFromNetwork(): String {
delay(1000)
return "새 데이터"
}
fun main() = runBlocking {
// Avec cache - retour immédiat
val cached = fetchDataIfNeeded("캐시 데이터")
println(cached)
// Sans cache - requête réseau
val fresh = fetchDataIfNeeded(null)
println(fresh)
}
Chaînage
suspend fun step1(): Int {
delay(500)
return 10
}
suspend fun step2(input: Int): Int {
delay(500)
return input * 2
}
suspend fun step3(input: Int): Int {
delay(500)
return input + 5
}
suspend fun processChain(): Int {
val a = step1()
val b = step2(a)
val c = step3(b)
return c
}
fun main() = runBlocking {
val result = processChain()
println("Résultat final : $result") // 25 (10 * 2 + 5)
}
Gestion des erreurs
suspend fun riskyOperation(): String {
delay(1000)
throw Exception("Erreur réseau !")
}
suspend fun safeOperation(): String? {
return try {
riskyOperation()
} catch (e: Exception) {
println("Erreur survenue : ${e.message}")
null
}
}
fun main() = runBlocking {
val result = safeOperation()
println("Résultat : ${result ?: "Échec"}")
}
🔥 Fonctions utiles
Gestion du timeout
suspend fun fetchWithTimeout(): String? {
return try {
withTimeout(2000) {
delay(3000) // Prend trop de temps
"데이터"
}
} catch (e: TimeoutCancellationException) {
println("Temps dépassé !")
null
}
}
fun main() = runBlocking {
val result = fetchWithTimeout()
println("Résultat : ${result ?: "Timeout"}")
}
Logique de retry
suspend fun <T> retry(
times: Int = 3,
initialDelay: Long = 100,
factor: Double = 2.0,
block: suspend () -> T
): T {
var currentDelay = initialDelay
repeat(times - 1) { attempt ->
try {
return block()
} catch (e: Exception) {
println("Tentative ${attempt + 1} échouée : ${e.message}")
}
delay(currentDelay)
currentDelay = (currentDelay * factor).toLong()
}
return block() // Dernière tentative
}
var attemptCount = 0
suspend fun unstableAPI(): String {
attemptCount++
if (attemptCount < 3) {
throw Exception("Erreur temporaire")
}
return "Succès !"
}
fun main() = runBlocking {
val result = retry(times = 5) {
unstableAPI()
}
println(result)
}
Pattern de cache
class DataRepository {
private var cache: String? = null
suspend fun getData(forceRefresh: Boolean = false): String {
if (!forceRefresh && cache != null) {
println("Retour depuis le cache")
return cache!!
}
println("Récupération depuis le réseau...")
delay(1000)
val data = "새 데이터"
cache = data
return data
}
}
fun main() = runBlocking {
val repo = DataRepository()
// Premier appel - réseau
println(repo.getData())
// Deuxième appel - cache
println(repo.getData())
// Rafraîchissement forcé
println(repo.getData(forceRefresh = true))
}
🛡️ Fonctions annulables
Vérification d'annulation
suspend fun longRunningTask() {
repeat(10) { i ->
if (!isActive) {
println("Annulé !")
return
}
println("Tâche $i")
delay(500)
}
}
fun main() = runBlocking {
val job = launch {
longRunningTask()
}
delay(2000)
println("Annulation de la tâche !")
job.cancelAndJoin()
}
ensureActive
suspend fun heavyWork() {
repeat(10) { i ->
ensureActive() // Lance une exception si annulé
println("Travail lourd $i")
Thread.sleep(500) // Simulation de travail CPU
}
}
fun main() = runBlocking {
val job = launch {
try {
heavyWork()
} catch (e: CancellationException) {
println("Le travail a été annulé")
}
}
delay(2000)
job.cancel()
}
🤔 Questions fréquentes
Q1. Les fonctions suspend créent-elles des threads ?
R : Non ! suspend ne fait que suspendre sans bloquer le thread.
suspend fun example() {
println("Début : ${Thread.currentThread().name}")
delay(1000)
println("Fin : ${Thread.currentThread().name}")
}
// Peut s'exécuter sur le même thread !
Q2. Et si on rend toutes les fonctions suspend ?
R : Ce n'est pas nécessaire ! Utilisez-le uniquement quand vous avez des opérations asynchrones.
// ❌ Inutile
suspend fun add(a: Int, b: Int) = a + b
// ✅ Nécessaire
suspend fun fetchData() = withContext(Dispatchers.IO) {
// Requête réseau
}
Q3. Peut-on appeler une fonction normale depuis une fonction suspend ?
R : Bien sûr que oui !
fun normalFunction() = println("Fonction normale")
suspend fun suspendFunction() {
normalFunction() // OK !
delay(1000)
}
🎬 Conclusion
Écrivez du code asynchrone efficace avec les fonctions suspend !
Résumé :
✅ Suspension possible avec suspend
✅ Attente sans bloquer le thread
✅ Changement de thread avec withContext
✅ Contrôle de l'exécution séquentielle/parallèle
✅ Tâches annulables
Prochaine étape : Découvrez les flux de données asynchrones avec Flow !