欢迎光临
我们一直在努力

kotlin 协程 - 核心概念

Kotlin 协程(Coroutine)

1. 协程基础概念

协程是什么?与线程的区别?

维度线程协程
调度 操作系统内核调度 用户态调度(Kotlin 由 Dispatcher 调度)
切换成本 高(涉及内核态切换) 低(用户态切换)
数量 有限(几千个就占满内存) 可创建百万级
阻塞 阻塞线程会影响其他任务 挂起(suspend)不阻塞线程

核心概念(看底部说明):

  • suspend:挂起函数,可在不阻塞线程的情况下暂停执行

  • CoroutineScope:协程作用域,管理协程生命周期

  • Job:协程的句柄,可控制取消

  • Dispatcher:调度器,决定协程在哪个线程执行

kotlin

// 三种标准 Dispatcher
Dispatchers.Main // Android 主线程,更新 UI
Dispatchers.IO // 适合 IO 密集型(网络、数据库、文件)
Dispatchers.Default // 适合 CPU 密集型(排序、JSON 解析、图片处理)
Dispatchers.Unconfined // 从当前线程启动,挂起后在任意线程恢复(一般不用)


2. 启动协程的方式

launch、async、runBlocking 的区别?

kotlin

// launch:启动一个新协程,返回 Job,不返回结果
val job = scope.launch {
delay(1000L)
println("World!")
}

// async:启动协程,返回 Deferred(可等待的结果)
val deferred = scope.async {
fetchData() // 耗时操作
}
val result = deferred.await() // 挂起等待结果

// runBlocking:阻塞当前线程直到协程完成(仅用于测试或桥接代码)
runBlocking {
delay(1000L)
println("Done")
}

关键区别:

  • launch:fire-and-forget,异常会立即向上传播

  • async:用于并发执行并获取结果,异常在 await() 时抛出;建议使用 supervisorScope 避免一个 async 失败影响其他


3. 协程取消与异常

协程如何取消?为什么有时取消不了?

kotlin

val job = scope.launch {
try {
repeat(1000) { i ->
println("job: I'm sleeping $i …")
delay(500L) // 挂起函数会检查取消状态
}
} finally {
// 清理资源
withContext(NonCancellable) {
println("job: I'm running finally")
delay(1000L)
println("job: And I've just delayed for 1 sec because I'm non-cancellable")
}
}
}
delay(1300L)
job.cancelAndJoin() // 取消并等待完成

// 无法取消的情况:阻塞操作不会检查取消状态
scope.launch {
while (isActive) { // 应使用 isActive 检查状态
// 耗时计算,不会自动取消
}
}

// 解决方案:使计算可取消
while (isActive) {
// 或者使用 yield() 检查取消
yield()
}

取消传播规则:

  • 父协程取消 → 所有子协程取消

  • 子协程异常 → 默认取消父协程和所有兄弟协程(可通过 SupervisorJob 改变)


4. 协程作用域与结构化并发

SupervisorJob 与 Job 的区别?supervisorScope 的作用?

kotlin

// Job:一个子协程失败,所有兄弟协程和父协程都取消
val scope = CoroutineScope(Job() + Dispatchers.Main)

// SupervisorJob:一个子协程失败,不影响其他子协程
val supervisorScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

// supervisorScope:用于 async 并发时隔离异常
supervisorScope {
val deferred1 = async { fetchUser() }
val deferred2 = async { fetchOrders() }

// 一个失败不影响另一个
val user = try { deferred1.await() } catch (e: Exception) { null }
val orders = try { deferred2.await() } catch (e: Exception) { null }
}

Android 中的 Scope:

kotlin

// ViewModel 中使用 viewModelScope(自动在 ViewModel 清除时取消)
class MyViewModel : ViewModel() {
fun loadData() {
viewModelScope.launch {
val data = repository.fetchData()
_uiState.value = data
}
}
}

// LifecycleOwner 中使用 lifecycleScope
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
lifecycleScope.launch {
// 自动在 Activity 销毁时取消
}

// 特定生命周期状态启动
lifecycleScope.launchWhenStarted {
// 仅在 STARTED 及以上状态执行
}
}
}


5. Flow(响应式流)

Flow、LiveData、RxJava 的区别?冷流与热流?

kotlin

// Flow:冷流,每次 collect 都会重新执行构建代码
fun fetchData(): Flow<String> = flow {
for (i in 1..3) {
delay(100)
emit("item $i") // 发射数据
}
}.flowOn(Dispatchers.IO) // 指定上游执行线程

// 使用
lifecycleScope.launch {
fetchData()
.filter { it.contains("1") }
.map { it.uppercase() }
.catch { e -> emit("Error: ${e.message}") } // 异常处理
.collect { value -> // 终端操作,触发执行
println(value)
}
}

// StateFlow:热流,有状态,新订阅者立即获得当前值
private val _state = MutableStateFlow(UiState())
val state: StateFlow<UiState> = _state.asStateFlow()

// SharedFlow:热流,可配置 replay、buffer
private val _events = MutableSharedFlow<Event>(replay = 0)
val events: SharedFlow<Event> = _events.asSharedFlow()

Flow vs LiveData:

特性FlowLiveData
生命周期感知 需配合 repeatOnLifecycle 内置
线程 可在任意线程发射/收集 必须在主线程
组合操作 丰富的操作符 有限
背压 支持多种策略 不支持

kotlin

// Flow 生命周期安全收集
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
updateUi(state)
}
}
}

除了你提到的四个,Kotlin 协程的核心概念还包括:

上下文与组合

CoroutineContext(协程上下文)

  • 协程运行环境的"容器",包含 Dispatcher、Job、CoroutineName、CoroutineExceptionHandler 等元素的集合

  • 通过 + 运算符组合:Dispatchers.IO + Job() + CoroutineName("myJob")

Continuation(续体)

  • 挂起函数编译后的底层表示,保存了挂起点的状态与恢复逻辑

  • suspend 的本质就是 Continuation<T>.(T) -> Unit 的 CPS 变换

启动与并发

async / await

  • async 启动一个协程并返回 Deferred<T>(带结果的 Job)

  • await() 非阻塞地等待结果,是协程并发组合的关键

launch

  • 启动"即发即忘"的协程,返回 Job,不携带结果值

runBlocking

  • 在普通阻塞代码与协程世界之间搭桥的函数,会阻塞当前线程直到内部协程完成

  • 主要用于 main 函数和单元测试,生产代码中应避免

withContext

  • 在指定调度器中执行代码块并返回结果,执行完后自动切回原上下文

  • 常用于 IO 操作:withContext(Dispatchers.IO) { … }

生命周期与异常

SupervisorJob / supervisorScope

  • 监督作用域:子协程失败不会取消兄弟协程,适用于"一个模块挂了不影响其他模块"的场景

CoroutineExceptionHandler

  • 捕获未处理异常的处理器,必须安装在 CoroutineScope 的上下文中

CancellationException

  • 协程取消时抛出的特殊异常,通常不需要捕获处理

NonCancellable

  • 一种特殊的 CoroutineContext,让代码块在协程取消时仍能执行完清理逻辑

数据流与通信

Flow(冷流)

  • 类似 RxJava 的响应式流,按需生产数据,使用 collect 终端操作触发执行

  • 操作符:map、filter、flatMapMerge、catch、onCompletion 等

SharedFlow / StateFlow(热流)

  • SharedFlow:多播事件流,有缓存策略

  • StateFlow:带最新值的 SharedFlow,是 LiveData 的协程替代,支持状态管理

Channel

  • 协程间的通信原语,类似阻塞队列但完全非阻塞

  • 类型:Channel.UNLIMITED、Channel.CONFLATED、Channel.BUFFERED

select

  • 多路复用,同时等待多个挂起操作(多个 Channel 接收、多个 Deferred await),哪个先就绪就执行哪个

同步原语

Mutex

  • 协程级别的互斥锁,withLock { } 挂起而非阻塞线程

Semaphore

  • 信号量,控制并发访问数量

其他重要概念

yield()

  • 挂起函数,主动让出当前协程的执行权,让同调度器上的其他协程有机会运行

delay()

  • 非阻塞延迟,与 Thread.sleep() 不同,不会占用线程资源

CoroutineStart

  • 启动模式:DEFAULT(立即调度)、LAZY(惰性启动,需调用 start/await)、ATOMIC(取消前必须开始)、UNDISPATCHED(先在当前线程执行到第一个挂起点)

suspendCancellableCoroutine / suspendCoroutine

  • 将回调式 API(如 Android 的 Retrofit 回调)桥接为挂起函数的底层工具


一句话总结:你列出的四个是骨架,而 CoroutineContext(环境配置)、Flow/Channel(数据传递)、async/await(并发组合)、SupervisorJob/ExceptionHandler(容错治理)和 Continuation(底层机制)共同构成了协程的完整生态。

赞(0)
未经允许不得转载:171主机测评 » kotlin 协程 - 核心概念
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址