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. 启动协程的方式

launchasyncrunBlocking 的区别?

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. 协程作用域与结构化并发

SupervisorJobJob 的区别?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(响应式流)

FlowLiveDataRxJava 的区别?冷流与热流?

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

特性 Flow LiveData
生命周期感知 需配合 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 终端操作触发执行

  • 操作符:mapfilterflatMapMergecatchonCompletion

SharedFlow / StateFlow(热流)

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

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

Channel

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

  • 类型:Channel.UNLIMITEDChannel.CONFLATEDChannel.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(底层机制)共同构成了协程的完整生态。

相关推荐
小乖写代码2 小时前
49款开源Android小游戏
android·游戏
三少爷的鞋3 小时前
小团队的最优解:单 Module + 包内 Clean 架构
android
爱和冰阔落3 小时前
【Linux】两个毫无关系的进程怎么通信?命名管道 FIFO 从原理到 Server/Client 实战
android·linux·数据库·c++·vim
年小个大12 小时前
受 go-zero 启发,我给 Flutter 写了一套 CLI 脚手架
android·flutter·架构
Android-Flutter13 小时前
为什么说ActivityThread是主线程?
android·kotlin
余额瞒着我当琳15 小时前
C++模板初阶与STL初探
android·java·c++
知行合一。。。18 小时前
DeepAgents--01--Agent和OpenClaw的相关概念
android·数据库
汪海游龙19 小时前
告别 Play Console 手动上传:从模拟器截图到自动发版的完整流水线
android·ci/cd·github
weixin_4407841119 小时前
【IntentSeivice实现原理】
android·java·开发语言·intentservice