⏸️ 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))
}