跳至正文

⏸️ suspend 函數

📖 什麼是 suspend?

suspend 函數是可以暫停協程的特殊函數。它不會阻塞執行緒就能等待,因此非常高效!

💡 基本概念

suspend 關鍵字

// suspend 函數可以呼叫其他 suspend 函數
suspend fun doSomething() {
delay(1000) // suspend 函數
println("完成!")
}

fun main() = runBlocking {
doSomething() // 在協程內呼叫
}

一般函數 vs suspend 函數

// ❌ 一般函數無法呼叫 suspend 函數
fun normalFunction() {
// delay(1000) // 編譯錯誤!
}

// ✅ suspend 函數可以
suspend fun suspendFunction() {
delay(1000) // OK!
}

🎯 實戰範例

網路請求

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

suspend fun fetchUser(userId: String): User {
delay(1000) // 模擬網路延遲
return User(userId, "洪吉童")
}

fun main() = runBlocking {
println("開始請求")
val user = fetchUser("user123")
println("收到使用者:${user.name}")
}

循序執行

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

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

suspend fun prepareMeal() {
println("開始準備")

val food = prepareFood()
println("$food 準備完成")

val table = setTable()
println("$table 準備完成")

println("所有準備完成!")
}

fun main() = runBlocking {
val time = measureTimeMillis {
prepareMeal()
}
println("耗時:${time}ms") // ~1500ms
}

平行執行

suspend fun prepareMealParallel() = coroutineScope {
println("開始準備")

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

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

println("$food$table 準備完成")
}

fun main() = runBlocking {
val time = measureTimeMillis {
prepareMealParallel()
}
println("耗時:${time}ms") // ~1000ms(平行!)
}

🔧 withContext

切換調度器

suspend fun loadData(): String {
return withContext(Dispatchers.IO) {
// 在 IO 執行緒執行
delay(1000)
"資料"
}
}

fun main() = runBlocking {
println("主執行緒:${Thread.currentThread().name}")

val data = loadData()

println("回到主執行緒:${Thread.currentThread().name}")
println("資料:$data")
}

多個上下文

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

// CPU 密集型操作
val processed = withContext(Dispatchers.Default) {
heavyComputation(data)
}

// 主執行緒(UI 更新等)
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("UI 更新:$data")
}

🎨 進階模式

條件式等待

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

suspend fun fetchFromNetwork(): String {
delay(1000)
return "新資料"
}

fun main() = runBlocking {
// 有快取 - 立即返回
val cached = fetchDataIfNeeded("快取資料")
println(cached)

// 無快取 - 發送網路請求
val fresh = fetchDataIfNeeded(null)
println(fresh)
}

鏈接

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

錯誤處理

suspend fun riskyOperation(): String {
delay(1000)
throw Exception("網路錯誤!")
}

suspend fun safeOperation(): String? {
return try {
riskyOperation()
} catch (e: Exception) {
println("發生錯誤:${e.message}")
null
}
}

fun main() = runBlocking {
val result = safeOperation()
println("結果:${result ?: "失敗"}")
}

🔥 實用函數

超時處理

suspend fun fetchWithTimeout(): String? {
return try {
withTimeout(2000) {
delay(3000) // 耗時太久
"資料"
}
} catch (e: TimeoutCancellationException) {
println("超過時間!")
null
}
}

fun main() = runBlocking {
val result = fetchWithTimeout()
println("結果:${result ?: "逾時"}")
}

重試邏輯

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("嘗試 ${attempt + 1} 失敗:${e.message}")
}
delay(currentDelay)
currentDelay = (currentDelay * factor).toLong()
}
return block() // 最後一次嘗試
}

var attemptCount = 0

suspend fun unstableAPI(): String {
attemptCount++
if (attemptCount < 3) {
throw Exception("暫時性錯誤")
}
return "成功!"
}

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

快取模式

class DataRepository {
private var cache: String? = null

suspend fun getData(forceRefresh: Boolean = false): String {
if (!forceRefresh && cache != null) {
println("從快取返回")
return cache!!
}

println("從網路取得中...")
delay(1000)
val data = "新資料"
cache = data
return data
}
}

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

// 第一次呼叫 - 網路
println(repo.getData())

// 第二次呼叫 - 快取
println(repo.getData())

// 強制重新整理
println(repo.getData(forceRefresh = true))
}

🛡️ 可取消的函數

檢查取消

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

println("作業 $i")
delay(500)
}
}

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

delay(2000)
println("取消作業!")
job.cancelAndJoin()
}

ensureActive

suspend fun heavyWork() {
repeat(10) { i ->
ensureActive() // 取消時拋出例外

println("繁重作業 $i")
Thread.sleep(500) // 模擬 CPU 作業
}
}

fun main() = runBlocking {
val job = launch {
try {
heavyWork()
} catch (e: CancellationException) {
println("作業已被取消")
}
}

delay(2000)
job.cancel()
}

🤔 常見問題

Q1. suspend 函數會建立執行緒嗎?

**A:**不會!suspend 只會暫停,而不會阻塞執行緒。

suspend fun example() {
println("開始:${Thread.currentThread().name}")
delay(1000)
println("結束:${Thread.currentThread().name}")
}
// 可能在同一個執行緒執行!

Q2. 如果把所有函數都改成 suspend?

**A:**不必要!只在有非同步操作時使用。

// ❌ 不必要
suspend fun add(a: Int, b: Int) = a + b

// ✅ 必要
suspend fun fetchData() = withContext(Dispatchers.IO) {
// 網路請求
}

Q3. suspend 函數可以呼叫一般函數嗎?

**A:**當然可以!

fun normalFunction() = println("一般函數")

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

🎬 結語

用 suspend 函數打造高效的非同步程式碼!

核心總結:
✅ 用 suspend 實現暫停
✅ 不阻塞執行緒的等待
✅ 用 withContext 切換執行緒
✅ 控制循序/平行執行
✅ 可取消的作業

下一步:在 Flow 中了解非同步資料流!