ToolkitX
知识库工具箱

协程深入

suspend, Flow, Channel, 结构化并发

30min·高级

01. 协程基础

协程是 Kotlin 的轻量级并发方案。相比于线程,一个线程上可以跑成千上万个协程,因为协程挂起时不阻塞线程,线程可以去干别的事。suspend 标记可挂起函数,launch 启动不关心返回值的协程,async 启动并返回 Deferred(类似 Future)。协程上下文决定了协程跑在哪个线程上。
kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    // launch 启动协程
    val job = launch {
        delay(1000)  // 挂起,不阻塞线程
        println("协程执行完毕")
    }
    
    // async 带返回值
    val deferred = async {
        delay(500)
        "结果"
    }
    
    println("等待结果...")
    val result = deferred.await()
    println("获得: $result")
    
    job.join()  // 等待 launch 协程结束
}
delay 是协程的 sleep,挂起不阻塞线程;Thread.sleep 是真正阻塞线程。

02. 协程上下文和调度器

协程调度器决定协程在哪个线程上跑。Dispatchers.Main 是 Android 主线程(更新 UI),Dispatchers.IO 处理网络和数据库,Dispatchers.Default 做 CPU 密集型计算,Dispatchers.Unconfined 不限定线程。withContext 可以临时切换上下文,不会创建新协程。
kotlin
fun main() = runBlocking {
    // 在 IO 线程做网络请求
    val data = withContext(Dispatchers.IO) {
        fetchFromNetwork()
    }
    
    // 回到主线程更新 UI
    withContext(Dispatchers.Main) {
        showData(data)
    }
    
    // 组合使用
    launch(Dispatchers.Default) {
        // CPU 密集计算
        val result = heavyComputation()
        withContext(Dispatchers.Main) {
            updateUI(result)
        }
    }
}

suspend fun fetchFromNetwork(): String {
    delay(500)
    return "网络数据"
}

fun showData(data: String) = println(data)
fun heavyComputation(): String = "计算完成"
fun updateUI(result: String) = println("UI: $result")
withContext 挂起当前协程,在指定调度器执行代码块,完成后回来继续,非常优雅。

03. 协程作用域和生命周期

协程作用域控制协程的生命周期。GlobalScope 是全局作用域(不推荐,生命周期不可控)。lifecycleScope 绑定 Android 的 Activity/Fragment 生命周期。viewModelScope 绑定 ViewModel 的生命周期。结构化并发:父协程会等所有子协程结束,子协程出错父协程也会取消。
kotlin
// Activity 中使用 lifecycleScope
class MyActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        lifecycleScope.launch {
            // Activity 销毁时自动取消
            val data = loadData()
            updateUI(data)
        }
    }
}

// ViewModel 中使用 viewModelScope
class MyViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch {
            // ViewModel 清除时自动取消
            val users = fetchUsers()
            _users.value = users
        }
    }
}

// 自定义 CoroutineScope
class DataLoader {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    
    fun load() {
        scope.launch {
            // 手动管理生命周期
        }
    }
    
    fun destroy() {
        scope.cancel()  // 取消所有协程
    }
}
不要用 GlobalScope,它的生命周期和 Application 一样长,容易造成资源泄漏。

04. 异常处理和取消

协程的异常分两种:launch 的异常会直接抛给父协程导致整个作用域取消,async 的异常封装在 await 时才抛出。SupervisorJob 防止一个子协程失败影响其他子协程。用 try-catch 包裹协程内部代码处理异常,CoroutineExceptionHandler 做全局异常兜底。
kotlin
fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception ->
        println("捕获异常: ${exception.message}")
    }
    
    // SupervisorJob 隔离错误
    val scope = CoroutineScope(SupervisorJob() + handler)
    
    scope.launch {
        delay(100)
        throw RuntimeException("子协程1崩了")
    }
    
    scope.launch {
        delay(200)
        println("子协程2不受影响,正常执行")
    }
    
    delay(500)
    
    // 取消协程
    val job = launch {
        try {
            repeat(1000) { i ->
                println("工作 $i")
                delay(100)
            }
        } finally {
            println("清理资源")
        }
    }
    
    delay(300)
    job.cancel()  // 取消
    println("已取消")
}
协程取消是协作式的:只有挂起点才会检查取消,纯计算不挂起的循环要手动检查 isActive。

05. Flow 冷数据流

Flow 是协程版的响应式流,类似 RxJava 但更轻量。flow {} 构建器创建数据流,emit 发射数据。collect 收集数据时才执行(冷流)。操作符 map/filter/take 等中间处理,collect 才是终端操作。flowOn 改变执行上游的协程上下文。
kotlin
import kotlinx.coroutines.flow.*

fun getNumbers(): Flow<Int> = flow {
    for (i in 1..5) {
        delay(100)  // 模拟网络延迟
        emit(i)
    }
}

fun main() = runBlocking {
    getNumbers()
        .filter { it % 2 == 0 }
        .map { it * 10 }
        .collect { value ->
            println("收到: $value")
        }
    // 输出: 收到: 20, 收到: 40
    
    // StateFlow - 热流(有状态)
    val stateFlow = MutableStateFlow(0)
    stateFlow.value = 42
    
    // SharedFlow - 热流(事件)
    val events = MutableSharedFlow<String>()
    launch {
        events.collect { event -> println("事件: $event") }
    }
    events.emit("登录成功")
}
Flow 是冷流(被收集时才执行),StateFlow/SharedFlow 是热流(始终存在)。UI 状态用 StateFlow,一次性事件用 SharedFlow。

知识测验

1/4正确 0

协程的挂起函数 suspend 和普通函数的区别?

下一节

Kotlin + Spring Boot

下一节