什么是 suspend

● 核心概念: suspend 是 Kotlin 协程里标记“可挂起”的函数修饰符,表示该函数可以在不阻塞线程的情况下暂停与恢复执行。
● 本质: 不是新线程;只是让函数在挂起点让出线程,等结果回来再从挂起点继续。

如何声明与调用

suspend fun fetchUser(): User {
    // 可能会挂起的操作,例如网络/IO
    return api.getUser()
}

● 只能在以下地方调用 suspend 函数:
① 另一个 suspend 函数内部
② 协程构建器内:launch { … }、async { … }、runBlocking { … } 等

val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
scope.launch {
    val user = fetchUser() // 合法:在协程内
    show(user)
}

指定执行线程/调度器

● CPU 密集 → Dispatchers.Default
● IO 密集 → Dispatchers.IO
● UI 更新 → Dispatchers.Main

suspend fun load(): Data = withContext(Dispatchers.IO) {
    dao.query() // IO 线程执行
}

与阻塞的区别

● 挂起: 不阻塞线程,资源友好,适合大量并发
● 阻塞: Thread.sleep()、Future.get() 会占用线程,协程里尽量避免

delay(1000)        // 挂起1秒,不阻塞线程
Thread.sleep(1000) // 阻塞线程1秒

并发与结构化并发

suspend fun parallel(): Pair<A, B> = coroutineScope {
    val a = async(Dispatchers.IO) { loadA() }
    val b = async(Dispatchers.IO) { loadB() }
    a.await() to b.await()
}

● 使用 coroutineScope 限定生命周期,出错会取消子协程(结构化并发)。

异常处理与取消

val handler = CoroutineExceptionHandler { _, e -> log(e) }
scope.launch(handler) {
    try {
        doWork()
    } catch (e: CancellationException) {
        throw e // 取消异常要继续抛出,保持协程可被取消
    } catch (e: Exception) {
        // 业务异常处理
    }
}

● 协程可取消;挂起点(如 delay、withContext、suspendCancellableCoroutine)会检查取消。

在非挂起 API 中桥接挂起

suspend fun awaitCallback(): Result = suspendCancellableCoroutine { cont ->
    call.enqueue(object : Callback {
        override fun onSuccess(r: Result) {
            if (cont.isActive) cont.resume(r)
        }
        override fun onError(t: Throwable) {
            if (cont.isActive) cont.resumeWithException(t)
        }
    })
    cont.invokeOnCancellation { call.cancel() } // 取消协同
}

常见误区

● 在 suspend 里做重 CPU 任务却不切换到 Dispatchers.Default
● 在 suspend 里调用阻塞 API(如数据库/网络的阻塞实现)却不 withContext(Dispatchers.IO)
● 捕获并吞掉 CancellationException
● 滥用 GlobalScope,导致泄漏
● 用 runBlocking 卡住主线程(仅测试或入口场景使用)

launch、async和runBlocking的区别

● launch: 启动一个协程执行任务,不关心返回值。返回 Job。
● async: 启动一个协程并产生结果,需要 await() 获取。返回 Deferred。
● runBlocking: 在当前线程阻塞地运行协程,直到其内部执行完毕(一般只用于测试或main函数演示,Android主线程避免使用)。

构建器 用途 返回值 异常处理
launch 启动任务,不关心结果 Job 立即传播
async 启动任务,需要结果 Deferred 延迟传播
runBlocking 测试/桥接阻塞代码 T 立即传播
  1. launch

● 用途: 火并执行任务、更新UI、发送事件等“只做事不取值”的场景。
● 返回值: Job(可取消、可 join() 等待完成)。
● 异常: 未捕获异常会向上抛到其作用域的异常处理器(如 CoroutineExceptionHandler)。

val job = scope.launch(Dispatchers.IO) {
    doWork() // 无返回值
}
// 需要时可取消或等待
job.cancel()
job.join()
  1. async

● 用途: 并发执行并“需要结果”的子任务,配合 await()。
● 返回值: Deferred(是 Job 的子类型)。
● 异常: 会在 await() 时抛出(延迟传播),可在 await() 处 try/catch

val r1 = scope.async(Dispatchers.IO) { loadA() }
val r2 = scope.async(Dispatchers.IO) { loadB() }
val result = r1.await() + r2.await()
  1. runBlocking

● 用途: 把协程世界“桥接”到阻塞世界;常用于单元测试、fun main() 示例。
● 行为: 阻塞当前线程直到其代码块结束;Android 主线程请勿使用(会卡UI)。

fun main() = runBlocking {
    val value = async { compute() }.await()
    println(value)
}

coroutineScope和supervisorScope的区别

构建器 用途 返回值 异常处理 适用场景
coroutineScope 结构化并发 T 立即 需要所有子任务成功
supervisorScope 监督并发 T 隔离 部分失败可接受
  1. coroutineScope

● 用途: 创建新的协程作用域,用于结构化并发
● 返回值: T
● 异常处理: 立即传播异常,任何子协程失败都会取消整个作用域

suspend fun loadData(): Data = coroutineScope {
    val user = async { loadUser() }
    val config = async { loadConfig() }
    
    // 如果任何一个子协程失败,整个作用域都会被取消
    Data(user.await(), config.await())
}

特点:
● 等待所有子协程完成
● 任何子协程失败都会取消其他子协程
● 适合需要所有子任务都成功的场景

  1. supervisorScope

● 用途: 创建监督作用域,子协程失败不影响其他子协程
● 返回值: T
● 异常处理: 子协程异常不会影响其他子协程

suspend fun loadData(): Data = supervisorScope {
    val user = async { loadUser() }
    val config = async { loadConfig() }
    
    // 即使 loadUser() 失败,loadConfig() 仍会继续执行
    try {
        Data(user.await(), config.await())
    } catch (e: Exception) {
        // 处理部分失败的情况
        Data(null, config.await())
    }
}

特点:
● 子协程失败不会影响其他子协程
● 适合部分失败可接受的场景

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐