Zum Hauptinhalt springen

⏸️ suspend-Funktionen

📖 Was ist suspend?

suspend-Funktionen sind spezielle Funktionen, die Coroutines unterbrechen können. Sie sind effizient, weil sie warten können, ohne Threads zu blockieren!

💡 Grundkonzepte

Das suspend-Schlüsselwort

// suspend-Funktionen können andere suspend-Funktionen aufrufen
suspend fun doSomething() {
delay(1000) // suspend-Funktion
println("Fertig!")
}

fun main() = runBlocking {
doSomething() // Aufruf innerhalb einer Coroutine
}

Normale Funktion vs. suspend-Funktion

// ❌ Normale Funktionen können keine suspend-Funktionen aufrufen
fun normalFunction() {
// delay(1000) // Kompilierungsfehler!
}

// ✅ suspend-Funktionen können es
suspend fun suspendFunction() {
delay(1000) // OK!
}

🎯 Praxisbeispiele

Netzwerkanfragen

data class User(val id: String, val name: String)

suspend fun fetchUser(userId: String): User {
delay(1000) // Simulation von Netzwerkverzögerung
return User(userId, "Hong Gildong")
}

fun main() = runBlocking {
println("Anfrage gestartet")
val user = fetchUser("user123")
println("Empfangener Benutzer: ${user.name}")
}

Sequentielle Ausführung

suspend fun prepareFood(): String {
delay(1000)
return "Essen"
}

suspend fun setTable(): String {
delay(500)
return "Tisch"
}

suspend fun prepareMeal() {
println("Vorbereitung gestartet")

val food = prepareFood()
println("$food vorbereitet")

val table = setTable()
println("$table vorbereitet")

println("Alle Vorbereitungen abgeschlossen!")
}

fun main() = runBlocking {
val time = measureTimeMillis {
prepareMeal()
}
println("Benötigte Zeit: ${time}ms") // ~1500ms
}

Parallele Ausführung

suspend fun prepareMealParallel() = coroutineScope {
println("Vorbereitung gestartet")

val foodDeferred = async { prepareFood() }
val tableDeferred = async { setTable() }

val food = foodDeferred.await()
val table = tableDeferred.await()

println("$food, $table vorbereitet")
}

fun main() = runBlocking {
val time = measureTimeMillis {
prepareMealParallel()
}
println("Benötigte Zeit: ${time}ms") // ~1000ms (parallel!)
}

🔧 withContext

Dispatcher-Wechsel

suspend fun loadData(): String {
return withContext(Dispatchers.IO) {
// Ausführung im IO-Thread
delay(1000)
"Daten"
}
}

fun main() = runBlocking {
println("Haupt-Thread: ${Thread.currentThread().name}")

val data = loadData()

println("Zurück im Haupt-Thread: ${Thread.currentThread().name}")
println("Daten: $data")
}

Mehrere Kontexte

suspend fun processData() {
// IO-Operationen
val data = withContext(Dispatchers.IO) {
readFromFile()
}

// CPU-intensive Operationen
val processed = withContext(Dispatchers.Default) {
heavyComputation(data)
}

// Haupt-Thread (UI-Updates, etc.)
withContext(Dispatchers.Main) {
updateUI(processed)
}
}

suspend fun readFromFile(): String {
delay(500)
return "Dateiinhalt"
}

suspend fun heavyComputation(data: String): String {
delay(1000)
return data.uppercase()
}

suspend fun updateUI(data: String) {
println("UI-Update: $data")
}

🎨 Erweiterte Muster

Bedingte Wartezeit

suspend fun fetchDataIfNeeded(cache: String?): String {
return cache ?: fetchFromNetwork()
}

suspend fun fetchFromNetwork(): String {
delay(1000)
return "Neue Daten"
}

fun main() = runBlocking {
// Cache vorhanden - sofortige Rückgabe
val cached = fetchDataIfNeeded("Cache-Daten")
println(cached)

// Kein Cache - Netzwerkanfrage
val fresh = fetchDataIfNeeded(null)
println(fresh)
}

Verkettung

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("Endergebnis: $result") // 25 (10 * 2 + 5)
}

Fehlerbehandlung

suspend fun riskyOperation(): String {
delay(1000)
throw Exception("Netzwerkfehler!")
}

suspend fun safeOperation(): String? {
return try {
riskyOperation()
} catch (e: Exception) {
println("Fehler aufgetreten: ${e.message}")
null
}
}

fun main() = runBlocking {
val result = safeOperation()
println("Ergebnis: ${result ?: "Fehlgeschlagen"}")
}

🔥 Praktische Funktionen

Timeout-Behandlung

suspend fun fetchWithTimeout(): String? {
return try {
withTimeout(2000) {
delay(3000) // Dauert zu lange
"Daten"
}
} catch (e: TimeoutCancellationException) {
println("Zeitüberschreitung!")
null
}
}

fun main() = runBlocking {
val result = fetchWithTimeout()
println("Ergebnis: ${result ?: "Timeout"}")
}

Wiederholungslogik

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("Versuch ${attempt + 1} fehlgeschlagen: ${e.message}")
}
delay(currentDelay)
currentDelay = (currentDelay * factor).toLong()
}
return block() // Letzter Versuch
}

var attemptCount = 0

suspend fun unstableAPI(): String {
attemptCount++
if (attemptCount < 3) {
throw Exception("Vorübergehender Fehler")
}
return "Erfolg!"
}

fun main() = runBlocking {
val result = retry(times = 5) {
unstableAPI()
}
println(result)
}

Cache-Muster

class DataRepository {
private var cache: String? = null

suspend fun getData(forceRefresh: Boolean = false): String {
if (!forceRefresh && cache != null) {
println("Rückgabe aus Cache")
return cache!!
}

println("Abrufen aus Netzwerk...")
delay(1000)
val data = "Neue Daten"
cache = data
return data
}
}

fun main() = runBlocking {
val repo = DataRepository()

// Erster Aufruf - Netzwerk
println(repo.getData())

// Zweiter Aufruf - Cache
println(repo.getData())

// Erzwungene Aktualisierung
println(repo.getData(forceRefresh = true))
}

🛡️ Abbrechbare Funktionen

Abbruchprüfung

suspend fun longRunningTask() {
repeat(10) { i ->
if (!isActive) {
println("Abgebrochen!")
return
}

println("Aufgabe $i")
delay(500)
}
}

fun main() = runBlocking {
val job = launch {
longRunningTask()
}

delay(2000)
println("Aufgabe abbrechen!")
job.cancelAndJoin()
}

ensureActive

suspend fun heavyWork() {
repeat(10) { i ->
ensureActive() // Wirft Exception bei Abbruch

println("Schwere Arbeit $i")
Thread.sleep(500) // Simulation von CPU-Arbeit
}
}

fun main() = runBlocking {
val job = launch {
try {
heavyWork()
} catch (e: CancellationException) {
println("Aufgabe wurde abgebrochen")
}
}

delay(2000)
job.cancel()
}

🤔 Häufig gestellte Fragen

F1. Erstellen suspend-Funktionen Threads?

A: Nein! suspend blockiert keine Threads, sondern unterbricht nur die Ausführung.

suspend fun example() {
println("Start: ${Thread.currentThread().name}")
delay(1000)
println("Ende: ${Thread.currentThread().name}")
}
// Kann im gleichen Thread ausgeführt werden!

F2. Was, wenn man alle Funktionen zu suspend macht?

A: Unnötig! Verwenden Sie es nur, wenn Sie asynchrone Operationen haben.

// ❌ Unnötig
suspend fun add(a: Int, b: Int) = a + b

// ✅ Notwendig
suspend fun fetchData() = withContext(Dispatchers.IO) {
// Netzwerkanfrage
}

F3. Können suspend-Funktionen normale Funktionen aufrufen?

A: Natürlich!

fun normalFunction() = println("Normale Funktion")

suspend fun suspendFunction() {
normalFunction() // OK!
delay(1000)
}

🎬 Zusammenfassung

Effizienter asynchroner Code mit suspend-Funktionen!

Wichtige Punkte:
✅ Unterbrechung mit suspend möglich
✅ Warten ohne Thread-Blockierung
✅ Thread-Wechsel mit withContext
✅ Steuerung von sequentieller/paralleler Ausführung
✅ Abbrechbare Aufgaben

Nächster Schritt: Erfahren Sie mehr über asynchrone Datenströme in Flow!