协程
# 入门
# 协程 vs 回调
回调本身可以抛出和捕获异常,但异步回调执行时,发起调用处的 try/catch 通常不能捕获回调中后续抛出的异常。错误如何传递取决于具体回调 API。
协程可以把异步过程写成顺序形式,方便组合结果和处理异常;取消、异常传播及执行线程仍需要按协程 API 的规则管理。
# CoroutineScope
Kotlin 中, 所有协程都在 CoroutineScope 中运行。作用域在其整个作业期间会控制协程的生命周期。如果取消某个作用域的作业, 则该作用域内启动的所有协程也将取消
# 调度程序
Android 常用调度程序如下。它们不是全部选择,另外还有 Dispatchers.Unconfined 和自定义 dispatcher。
| 调度程序 | 常见用途 |
|---|---|
Dispatchers.Main | Android UI 更新及短小的主线程操作,需要平台提供 Main dispatcher |
Dispatchers.IO | 将阻塞式文件、网络等 I/O 工作移出主线程 |
Dispatchers.Default | CPU 密集型工作,例如大规模计算 |
dispatcher 决定协程如何调度,并不保证每次调用都切换到另一条线程。参考:协程上下文与调度程序 (opens new window)。
# WithContext (opens new window):
withContext 在指定上下文中执行代码并返回结果,常用于选择执行工作的 dispatcher。它不一定切换线程;上下文相同或调度器共享线程时,也可能继续在原线程执行。
# Continuation
Continuation 表示挂起计算的后续执行,包含恢复所需的上下文以及接收结果或异常的 resumeWith。它不是一条线程;业务代码通常通过协程构建器和挂起 API 使用它,而不是手工管理恢复过程。
# 其他
# suspend
suspend 表示函数允许调用挂起 API,通常从协程或其他挂起函数中调用;它不保证函数会真正挂起,也不自动切换到后台线程。
| 函数中的工作 | 对当前线程的影响 |
|---|---|
| 普通计算或立即返回 | 同步执行,不一定出现挂起 |
delay 等非阻塞挂起 API | 等待期间挂起协程,线程可以执行其他任务 |
Thread.sleep、阻塞式文件读取 | 即使外层函数标记为 suspend,仍会阻塞执行这些调用的线程 |
例如,阻塞文件读取可由挂起函数内部负责切换到 IO dispatcher,让调用方在主线程调用时不会阻塞 UI:
import java.io.File
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
suspend fun readText(file: File): String = withContext(Dispatchers.IO) {
file.readText()
}
2
3
4
5
6
7
这里的文件读取仍是阻塞操作,只是执行在 IO dispatcher 上。非阻塞挂起与把阻塞操作移到后台是不同机制。
来源:Android 协程与主线程安全 (opens new window)、Kotlin 挂起函数规范 (opens new window)。