CoroutineContext
): 可以看作是协程的“配置信息”集合,它包含了协程的 Job
、调度器 (Dispatcher
)、名称、异常处理器等元素。CoroutineDispatcher
): CoroutineContext
的一个核心元素,它决定了协程在哪个或哪些线程上执行。常见的调度器包括:
Dispatchers.Main
: 主线程调度器。在 Android 中,它通常是 UI 线程,专用于执行更新界面的操作。Dispatchers.IO
: IO 调度器。专为执行磁盘或网络 I/O 等阻塞式、耗时操作而设计和优化,其背后是一个弹性的共享线程池。Dispatchers.Default
: 默认调度器。专为 CPU 密集型计算任务而设计,其线程池大小与 CPU 核心数相关。Dispatchers.Unconfined
: 非受限调度器。它不将协程限制在任何特定的线程池中,协程会先在启动它的线程中执行,挂起后则由恢复它的线程继续执行,其行为难以预测,通常不推荐在常规代码中使用。withContext
是一个挂起函数,它的核心作用是在不引入新协程的情况下,将代码块切换到指定的 CoroutineDispatcher
上下文执行,并在执行完毕后自动切回原来的上下文。
withContext
的优雅之处在于其对挂起和恢复机制的运用(基于 Continuation Passing Style, CPS)。当调用 withContext
时:
Continuation
)调度到目标 Dispatcher
的线程上执行。Dispatcher
上恢复之前挂起的协程。最重要的是,withContext
会返回其代码块中最后一条语句的执行结果。
经典用例:在 ViewModel 中安全地请求网络
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MyViewModel : ViewModel() {
fun fetchAndShowData() {
// 在 viewModelScope (通常是 Dispatchers.Main.immediate) 中启动协程
viewModelScope.launch {
// UI 线程,可以显示加载动画
showLoading()
// 切换到 IO 线程执行网络请求
val result = withContext(Dispatchers.IO) {
// 现在处于 IO 线程,可以安全地执行阻塞式网络调用
myApi.fetchData() // 这是一个耗时的 suspend 或阻塞函数
} // withContext 代码块结束,自动切回 Main 线程
// 已自动切回 UI 线程,可以安全地更新 UI
updateUi(result)
hideLoading()
}
}
// 模拟的 UI 和 API 方法
private fun showLoading() { /* ... */ }
private fun hideLoading() { /* ... */ }
private fun updateUi(data: String) { /* ... */ }
private object myApi { fun fetchData(): String = "Data from network" }
}
这是一个常见的混淆点,下表清晰地展示了它们的区别:
特性 | withContext(Dispatcher) |
launch(Dispatcher) |
---|---|---|
目的 | 切换当前协程的执行线程,以串行方式执行任务并获取结果。 | 启动一个新的并发子协程,让其独立运行。 |
行为 | 挂起函数 (suspend )。它会挂起当前协程,直到代码块执行完毕。 |
非阻塞。它会立即返回一个 Job 对象,不会挂起当前协程。 |
创建新协程? | 否。它只是在现有协程的上下文之间切换。 | 是。它会创建一个全新的、独立的子协程。 |
返回值 | 返回代码块的执行结果(类型为 T )。 |
返回一个 Job 对象,用于控制该协程的生命周期(取消、等待等)。 |
代码风格 | 使代码看起来像同步的、顺序执行,即使内部发生了线程切换。 | 适用于“即发即忘”或需要与当前代码并行执行的场景。 |
使用 withContext
:
suspend
函数中,需要执行一个耗时任务(如网络/数据库操作)并立即使用其结果来继续下一步逻辑时。使用 launch(Dispatcher)
:
简单来说,withContext
用于“在当前任务中切换线程办事并带回结果”,而 launch
用于“另起一个新任务让它自己去跑”。