Day5 Kotlin 协程

24 阅读6分钟

协程是Kotlin的异步编程解决方案,类似于Swift的 async/await

suspend 函数

suspend 是 Kotlin 协程的核心关键字,标记一个函数可以被挂起。挂起的意思是:函数执行到某个点时可以暂停,释放当前线程去做别的事,等结果回来再继续,整个过程不阻塞线程

// Kotlin
suspend fun fetchUser(): User { ... }
// Swift
func fetchUser() async -> User { ... }
`async` = `suspend`,概念完全一样

suspend 函数的规则

// Kotlin 
suspend fun loadData() {
    val user = fetchUser() // ✅ suspend 函数里调用 suspend 函数
}

fun normalFunc() {
    val user = fetchUser() // ❌ 编译报错,必须在协程或 suspend 函数里
}

// Swift — async 函数只能在 async 上下文调用
func loadData() async {
    let user = await fetchUser() // ✅
}

func normalFunc() {
    let user = await fetchUser() // ❌ 编译报错
}

启动协程

suspend 函数不能凭空调用,需要一个入口来启动协程,主要有三种方式。

  1. launch — 不关心返回值
// Kotlin
viewModelScope.launch {
    loadUser()
}
// Swift
Task {
    await loadUser()
}
`launch` 返回一个 `Job` 对象,可以用来取消,但**拿不到协程的执行结果**:
val job = viewModelScope.launch {
    loadUser()
}

job.cancel() // 可以取消
// job 拿不到 loadUser() 的返回值

  1. async — 关心返回值
// Kotlin
val deferred = viewModelScope.async {
    fetchUser()
}
val result = deferred.await() // 等待拿结果
对应 Swift 的 `async let`:
// Swift
async let user = fetchUser()
let result = await user
`async` 返回 `Deferred<T>`,调用 `.await()` 才能拿到值。
最常见的用法是并行请求
// Kotlin 
fun loadAll() {
    viewModelScope.launch {
        val user = async { fetchUser() }
        val posts = async { fetchPosts() }
        
        val u = user.await()
        val p = posts.await()
    }
}
// Swift 
func loadAll() async {
    async let user = fetchUser()
    async let posts = fetchPosts()
    
    let u = try? await user
    let p = try? await posts
}
  1. runBlocking — 测试专用

这个 Swift 没有对应的,它的作用是把协程变成阻塞调用,主要用于单元测试:

// 普通函数里没法调用 suspend 函数
// runBlocking 强行创建一个阻塞的协程环境
fun main() {
    runBlocking {
        val user = fetchUser() // 现在可以调用了
        println(user)
    }
}

⚠️ 生产代码里不要用,会阻塞线程,只在测试和 main 函数里用。


launch vs async 怎么选

需要返回值吗?
├── 不需要 → launch
└── 需要   → async + await

Scope 作用域

Scope 定义了协程的生命周期,协程必须在某个 Scope 里运行,Scope 取消时它里面所有协程都会取消。

viewModelScope

class UserViewModel : ViewModel() {
    fun loadUser() {
        viewModelScope.launch {
            val user = fetchUser()
            _uiState.value = user
        }
    }
}
// ViewModel 被清除时,所有协程自动取消
// Swift UIKit — VC 都 deinit 了,Task 还在跑
class UserViewController: UIViewController {
    func loadUser() {
        Task {
            let user = await fetchUser()
            self.updateUI(user) // VC 都没了还在回调,可能 crash
        }
    }
    
    // 要自己存 task 然后手动 cancel
    var task: Task<Void, Never>?
    
    override func viewDidDisappear(_ animated: Bool) {
        task?.cancel() // 还得记得取消
    }
}

lifecycleScope

用在 Activity / Fragment 里,和 Activity 生命周期绑定:

class UserActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 基础用法,Activity 销毁自动取消
        lifecycleScope.launch {
            loadData()
        }

        // 进阶用法:感知前后台
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // App 进后台自动暂停,回前台自动恢复
                viewModel.uiState.collect { state ->
                    updateUI(state)
                }
            }
        }
    }
}
// Swift UIKit — 你可能这样写过
class UserViewController: UIViewController {
    var task: Task<Void, Never>?

    override func viewDidAppear(_ animated: Bool) {
        task = Task {
            for await state in viewModel.uiStateStream {
                updateUI(state)
            }
        }
    }

    override func viewDidDisappear(_ animated: Bool) {
        task?.cancel() // 手动取消
    }
}
`lifecycleScope + repeatOnLifecycle` 把这套模板代码帮你封装好了。

CoroutineScope手动创建

用在普通类里,比如 Repository、Manager,这些类没有框架提供的 Scope:

class UserRepository {

    // 手动创建 scope
    private val scope = CoroutineScope(
        Dispatchers.IO + SupervisorJob()
    )

    fun startSync() {
        scope.launch {
            syncData()
        }
    }

    // ⚠️ 必须手动取消,否则内存泄漏
    fun destroy() {
        scope.cancel()
    }
}
// Swift
class UserRepository {
    var tasks: [Task<Void, Never>] = []

    func startSync() {
        let task = Task {
            await syncData()
        }
        tasks.append(task)
    }

    func destroy() {
        tasks.forEach { $0.cancel() }
    }
}
两者都需要手动管理,Kotlin 只是用 `scope.cancel()` 一次性取消所有。

GlobalScope — 几乎不用-----未完待续

coroutineScope {}supervisorScope {}

`coroutineScope` — 一个失败全取消
//Kotlin 对应 Swift 的 async let,行为完全一样
suspend fun loadAll(): Pair<User, List<Post>> = coroutineScope {
    val user = async { fetchUser() }
    val posts = async { fetchPosts() }
    // fetchUser 抛异常 → fetchPosts 也取消
    Pair(user.await(), posts.await())
}
// Swift async let 也是同样行为
func loadAll() async throws -> (User, [Post]) {
    async let user = fetchUser()
    async let posts = fetchPosts()
    // fetchUser 抛异常 → fetchPosts 也取消
    return try await (user, posts)
}
### `supervisorScope` — 失败互不影响

suspend fun loadAll() = supervisorScope { val user = async { fetchUser() } val posts = async { fetchPosts() } // fetchUser 抛异常 → fetchPosts 继续跑,互不影响

val u = runCatching { user.await() }.getOrNull()
val p = runCatching { posts.await() }.getOrNull()

}

Swift 没有直接对应,你之前用 `try?` 单独处理每个 `await` 来达到类似效果。
Scope 选择总结
你在哪写代码?
│
├── ViewModel 里          → viewModelScope
├── Activity/Fragment 里  → lifecycleScope  
├── 普通类里              → CoroutineScope(...) 手动管理
│
└── suspend 函数内部需要并发?
    ├── 一个失败全取消    → coroutineScope {}
    └── 失败互不影响      → supervisorScope {}

Dispatcher 调度器

Dispatcher 决定协程在哪个线程上运行。对应 Swift 里你切线程的方式。

Dispatchers.Main — 主线程

// Kotlin — 主线程,用来更新 UI
viewModelScope.launch(Dispatchers.Main) {
    textView.text = "Hello"
}

// 其实 viewModelScope 默认就是 Main
// 所以大部分时候不用显式写
viewModelScope.launch {
    textView.text = "Hello" // 已经在主线程了
}
// Swift — 切回主线程
await MainActor.run {
    self.tableView.reloadData()
}

Dispatchers.IO — IO 线程

// Kotlin — 网络请求、数据库、文件读写都用这个
viewModelScope.launch {
    val data = withContext(Dispatchers.IO) {
        api.fetchUser() // 切到 IO 线程
    }
    textView.text = data.name // 自动切回 Main 线程
}
背后有一个线程池,最多 64 个线程,专门处理等待 IO 的任务。
// Swift — 后台线程做网络/文件
Task.detached(priority: .background) {
    let data = await fetchFromNetwork()
}
`withContext` — 切换线程
这是最常用的切线程方式,对应 Swift`await MainActor.run {}`

Dispatchers.Default — CPU 密集线程

viewModelScope.launch {
    val result = withContext(Dispatchers.Default) {
        // 大量数据排序、图片处理、JSON 解析
        processLargeDataSet(data)
    }
    updateUI(result)
}
线程数和 CPU 核心数一样,专门跑计算密集的任务。
Swift 没有直接对应,用于**计算量大**的任务:

Dispatchers.Main → UI 操作、更新状态

Dispatchers.IO → 网络请求、数据库、文件

Dispatchers.Default → 排序、计算、图片处理

错误处理

基础 try/catch 和 Swift 的 do/try/catch 几乎一样:

// Kotlin — 结构完全一样
fun loadUser() {
    viewModelScope.launch {
        try {
            val user = fetchUser()
            updateUI(user)
        } catch (e: Exception) {
            showError(e)
        }
    }
}
// Swift
func loadUser() async {
    do {
        let user = try await fetchUser()
        updateUI(user)
    } catch {
        showError(error)
    }
}

runCatching — 对应 Swift 的 try?

// Kotlin — 失败返回 null
val user = runCatching { fetchUser() }.getOrNull()

// 还可以拿到错误信息
val result = runCatching { fetchUser() }
result.onSuccess { user -> updateUI(user) }
result.onFailure { e -> showError(e) }
// Swift — 失败返回 nil
let user = try? await fetchUser()

CoroutineExceptionHandler — 全局兜底

Swift 没有对应的,这是协程特有的全局错误处理机制:

 class UserViewModel : ViewModel() {

    private val handler = CoroutineExceptionHandler { _, e ->
        _uiState.value = UiState.Error(e.message)
    }

    fun loadUser() {
        viewModelScope.launch(handler) {
            val user = fetchUser()
            _uiState.value = UiState.Success(user)
        }
    }
}

launch vs async 的错误处理行为不一样 这是 Kotlin 特有的坑,Swift 没有这个区别:

// launch — 异常立刻抛出
viewModelScope.launch {
    throw Exception("出错了") // 立刻崩,必须在里面 catch
}

// async — 异常在 await() 时才抛出
viewModelScope.launch {
    val deferred = async {
        throw Exception("出错了") // 这里不崩
    }
    deferred.await() // 异常在这里才抛出
}
所以用 `async` 的时候要在 `await()` 外面 catch:
viewModelScope.launch {
    try {
        val deferred = async { fetchUser() }
        val user = deferred.await() // catch 要包这里
    } catch (e: Exception) {
        showError(e)
    }
}
错误处理选哪种
单个请求出错就展示错误   → try/catch
不关心错误只想要 null    → runCatching + getOrNull
全局兜底防止 crash       → CoroutineExceptionHandler

取消与超时

基础取消

// Kotlin
val job = viewModelScope.launch {
    loadUser()
}
job.cancel() // 取消
// Swift
let task = Task {
    await loadUser()
}
task.cancel() // 取消

取消是协作式的 这点和 Swift 完全一样,取消不是强制停止,协程需要配合才能真正取消

// Kotlin — 同样需要配合
suspend fun loadUser() {
    for (item in items) {
        if (!isActive) return // 主动检查是否还活着
        process(item)
    }
}
Kotlin 内置的 `suspend` 函数(比如 `delay`、`withContext`)都自动支持取消,不用手动检查。只有你自己写的循环逻辑才需要加 `isActive` 检查。
// Swift — 需要检查 isCancelled
func loadUser() async {
    for item in items {
        try Task.checkCancellation() // 主动检查
        await process(item)
    }
}

withTimeout — 超时取消

// Kotlin — 一行搞定
suspend fun fetchWithTimeout(): User {
    return withTimeout(3000) { // 3秒超时
        fetchUser()
    }
    // 超时抛出 TimeoutCancellationException
}

// 不想让超时抛异常,返回 null
val user = withTimeoutOrNull(3000) {
    fetchUser()
} // 超时返回 null,不崩
// Swift — 没有原生支持,要自己实现
func fetchWithTimeout() async throws -> User {
    try await withThrowingTaskGroup(of: User.self) { group in
        group.addTask { try await fetchUser() }
        group.addTask {
            try await Task.sleep(nanoseconds: 3_000_000_000)
            throw TimeoutError()
        }
        let result = try await group.next()!
        group.cancelAll()
        return result
    }
}

取消后的清理工作

// Kotlin — 用 try/finally
suspend fun loadUser() {
    try {
        showLoading()
        val user = fetchUser()
        updateUI(user)
    } finally {
        hideLoading() // 无论成功、失败、取消都执行
    }
}
`finally` 在协程被取消时也会执行,和 Swift 的 `defer` 一样可靠。

// Swift — 用 defer 清理
func loadUser() async {
    defer {
        hideLoading() // 无论成功还是取消都执行
    }
    showLoading()
    let user = try await fetchUser()
}