跳至正文

⏸️ 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 中了解异步数据流!