协程是什么:比线程更轻的并发单元

线程是操作系统调度的单位,开一个线程要占不少内存。协程则是 Kotlin 在用户态实现的"轻量线程"——你可以同时开成千上万个协程,它们跑在少数几个真正的线程上,由 Kotlin 自己调度。协程的核心价值:写异步代码像写同步一样顺,但不用真的占着线程死等。

对比一下:用 Thread 做网络请求,你得写回调、锁、join,层层嵌套;用协程,你直接写 val data = api.fetch()——看起来是同步的,但底层线程在等网络时去做别的事,结果回来再接着跑。

协程的本质:把"等待 I/O"这件事变成"挂起—恢复",线程不被占住,代码却读起来像同步。
本文核心知识点
  • launch起一个不返回结果的协程,即发即忘。
  • suspend标记一个函数可以被挂起,只能在协程里调。
  • async起一个带返回值的协程,用 await 拿结果。
  • Flow把多个异步结果像流水线一样逐个发出来。
Kotlin 协程配图:蓝调渐变背景上中央一个发光的调度核心方块,四周有多条彩色并发任务流线(青紫橙),每条线上有多个小任务节点流动,流线之间有汇合与分叉,外围有光圈和光点
中央调度核心驱动多条并发任务流线——这正是协程调度器的工作方式。

launch 与 runBlocking:怎么起一个协程

协程不能凭空跑,必须有一个"调度器"把它丢到线程上。最基础的启动方式是 runBlocking(测试用,会阻塞当前线程直到里面跑完)和 launch(真正的业务用,非阻塞)。

import kotlinx.coroutines.*

fun main() = runBlocking {            // 阻塞 main 等里面跑完
    launch {                          // 起一个协程
        delay(1000)                   // 挂起 1 秒,不占线程
        println("World")
    }
    print("Hello ")
}

这里 delay(1000) 是个 suspend 函数:它挂起协程 1 秒,期间线程去干别的。你不会真的卡 1 秒——和 Thread.sleep 本质不同。

suspend:标记一个可挂起的函数

你自己写的函数如果里面要 delay、要 await 网络结果,就给它加 suspend 前缀。这个关键字告诉编译器:"这个函数可能会挂起,只能在协程里调用"。

suspend fun fetchUser(): User {
    delay(500)                       // 模拟网络延迟
    return User("Alice")
}

// 只能在协程里调
runBlocking {
    val u = fetchUser()
    println(u.name)
}

关键心智:suspend 不会阻塞线程。它只是说"我可能要等一会儿,等的时候别占着线程"。这和回调地狱里"等完了调回调"是同一件事,只不过 Kotlin 把它写成了顺序代码。

async/await:并发拿结果

launch 不返回结果。要同时干两件事、最后汇总,用 async——它返回一个 Deferred 对象,用 await() 拿结果。

suspend fun loadBoth(): Pair<String, String> {
    val a = async { loadUser() }      // 开始跑,但不阻塞
    val b = async { loadOrder() }     // 同时开始跑
    return a.await() to b.await()      // 等两个都好
}

注意顺序:两个 async 必须先都"启动",再一起 await。如果你写成 a.await() 后才起 async { loadOrder() },那两个就是串行跑了——这点和 JavaScript 的 Promise.all 类似。

Flow:把一组异步数据流接住

协程解决"一次异步调用",Flow 解决"一连串异步值"——比如分页加载、实时推送、传感器数据流。它像一个可以异步产出多个元素的序列。

import kotlinx.coroutines.flow.*

fun numbers(): Flow<Int> = flow {
    for (i in 1..3) {
        delay(100)                    // 每发一个等 100ms
        emit(i)                       // 吐出一个值
    }
}

fun main() = runBlocking {
    numbers().collect { println(it) } // 逐个接住
}

flow { } 块里用 emit 产出值,外面用 collect 消费。中间还能用 map、filter 像 RxJava 那样串操作符——但底层是协程,没有 Rx 那种笨重的线程模型。

协程作用域:谁来管生命周期

协程需要一个"作用域"来管它的生命周期:界面关了,正在跑的网络请求应该自动取消,不然就泄漏了。Android 里常见 viewModelScope、lifecycleScope——ViewModel 销毁时里面所有协程自动取消。

class MyViewModel : ViewModel() {
    fun load() {
        viewModelScope.launch {       // 跟着 ViewModel 活
            val data = fetch()
            _uiState.value = data
        }
    }
}

记住一条:**永远在一个明确的 Scope 里起协程**,别在全局 GlobalScope 里乱起——那样你没法取消它,泄漏就在所难免。