Kotlin协程 ->withContext挂起函数详解
·
🔄 完整调用流程图
👤 用户代码调用
suspend fun fetchData() {
println("1. 开始 - Thread: main")
val result = withContext(Dispatchers.IO) {
delay(1000)
"Network Data"
}
println("5. 完成 - Thread: main, Result: $result")
}
↓
🎯 UserStateMachine.invokeSuspend() - Main线程
↓
❓ label=0: 首次执行
├── 🧵 println("1. 开始 - Thread: main")
├── 🔄 label = 1
└── 📞 调用 withContext(Dispatchers.IO) { ... }
↓
🔍 withContext() 上下文判断 - Main线程
↓
❓ 三种路径选择
├── 🚀 快速路径1: newContext === oldContext
│ └── ScopeCoroutine.startUndispatchedOrReturn() → 当前线程直接执行
│
├── 🚀 快速路径2: 调度器相同,其他元素变化
│ └── UndispatchedCoroutine.startUndispatchedOrReturn() → 当前线程执行
│
└── 🧵 慢速路径: 调度器变化 (Main → IO)
↓
🌉 创建 DispatchedCoroutine(IO上下文, UserStateMachine)
↓
🚀 block.startCoroutineCancellable(coroutine, coroutine)
↓
🏗️ createCoroutineUnintercepted() → WithContextBlockStateMachine
↓
🔗 .intercepted() → DispatchedContinuation(IO调度器)
↓
📤 .resumeCancellableWith(Unit) → 调度到 IO 线程
↓
⏸️ coroutine.getResult() → 返回 COROUTINE_SUSPENDED
↓
📍 UserStateMachine 检测到 COROUTINE_SUSPENDED → 用户状态机挂起
↓
🎯 IO 线程: WithContextBlockStateMachine.invokeSuspend()
↓
❓ Block 状态机执行
├── 🎯 label=0: 首次执行
│ ├── 🧵 println("2. Block开始 - Thread: DefaultDispatcher-worker-1")
│ ├── 🔄 label = 1
│ └── ⏸️ delay(1000) → 返回 COROUTINE_SUSPENDED
│ ↓
│ 📍 WithContextBlockStateMachine 挂起等待...
│ ↓
│ 🕐 Timer 1秒后触发 → CancellableContinuation.resume()
│ ↓
│ 🚀 WithContextBlockStateMachine.resumeWith(Unit)
│ ↓
└── ✅ label=1: delay 完成恢复
├── 🧵 println("3. Block恢复 - Thread: DefaultDispatcher-worker-1")
└── 🎯 return "Network Data"
↓
🔄 BaseContinuationImpl.resumeWith() 循环处理 - IO线程
↓
🌉 DispatchedCoroutine.resumeWith(Result.success("Network Data")) - IO线程
↓
🔧 makeCompleting() → afterResume() 处理完成 - IO线程
↓
🔍 uCont.intercepted() → DispatchedContinuation(Main调度器) - IO线程
↓
📤 intercepted.resumeWith(Result.success("Network Data")) → 调度到 Main 线程
↓
🎯 Main 线程: UserStateMachine.resumeWith(Result.success("Network Data"))
↓
🔄 BaseContinuationImpl.resumeWith() 循环开始 - Main线程
↓
⚙️ UserStateMachine.invokeSuspend(Result.success("Network Data")) - Main线程
↓
✅ label=1: withContext 完成恢复
├── 🎯 throwOnFailure(result) → 检查异常
├── 🎯 val data = result.getOrThrow() → "Network Data"
├── 🧵 println("5. 完成 - Thread: main, Result: $data")
└── 🏁 return Unit → 用户函数完成
🎯 withContext 核心源码方法明确讲解
阶段1:withContext 入口判断
public suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T {
return suspendCoroutineUninterceptedOrReturn { uCont ->
val oldContext = uCont.context // Main 上下文
val newContext = oldContext.newCoroutineContext(context) // Main + IO
// 🚀 快速路径1:上下文完全相同
if (newContext === oldContext) {
val coroutine = ScopeCoroutine(newContext, uCont)
return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
}
// 🚀 快速路径2:只有调度器相同(其他元素变化)
if (newContext[ContinuationInterceptor] == oldContext[ContinuationInterceptor]) {
val coroutine = UndispatchedCoroutine(newContext, uCont)
withCoroutineContext(coroutine.context, null) {
return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
}
}
// 🧵 慢速路径:调度器发生变化(Main → IO)
val coroutine = DispatchedCoroutine(newContext, uCont)
block.startCoroutineCancellable(coroutine, coroutine) // 立即调度
coroutine.getResult() // 返回 COROUTINE_SUSPENDED
}
}
阶段2:startCoroutineCancellable 立即调度
internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(
receiver: R, // DispatchedCoroutine
completion: Continuation<T> // DispatchedCoroutine
) = runSafely(completion) {
// 🏗️ 创建 WithContextBlockStateMachine
createCoroutineUnintercepted(receiver, completion)
.intercepted() // 🧵 获取 IO 调度器包装
.resumeCancellableWith(Result.success(Unit)) // 🚀 立即调度到 IO 线程
}
阶段3:IO 线程执行 Block 状态机
// 🎯 WithContextBlockStateMachine 在 IO 线程执行
class WithContextBlockStateMachine(
private val completion: Continuation<String> // DispatchedCoroutine
) : SuspendLambda {
override fun invokeSuspend(result: Result<Any?>): Any? {
when (label) {
0 -> {
// 🧵 在 IO 线程执行
println("Block executing - Thread: ${Thread.currentThread().name}")
// 输出:Block executing - Thread: DefaultDispatcher-worker-1
label = 1
// ⏸️ delay 在 IO 线程挂起
return delay(1000) // 返回 COROUTINE_SUSPENDED
}
1 -> {
// ✅ delay 完成后在 IO 线程恢复
println("Block resumed - Thread: ${Thread.currentThread().name}")
// 输出:Block resumed - Thread: DefaultDispatcher-worker-1
return "Network Data" // 返回结果给 DispatchedCoroutine
}
}
}
}
阶段4:DispatchedCoroutine 处理结果
internal class DispatchedCoroutine<T>(
context: CoroutineContext, // IO 上下文
private val uCont: Continuation<T> // 用户状态机 (Main 上下文)
) : ScopeCoroutine<T> {
// 🚀 Block 完成后在 IO 线程被调用
override fun resumeWith(result: Result<T>) {
val state = result.toState() // "Network Data"
if (makeCompleting(state)) return
// 🧵 需要切换回 Main 线程
afterResume(state)
}
override fun afterResume(state: Any?) {
val result = recoverResult(state, uCont)
val intercepted = uCont.intercepted() // Main 调度器包装
// 🚀 从 IO 线程调度到 Main 线程
intercepted.resumeWith(result)
}
}
阶段5:Main 线程恢复用户状态机
// 🧵 DispatchedContinuation 在 Main 线程执行
public final override fun run() {
val continuation = delegate.continuation // UserStateMachine
val result = recoverResult(takeState(), continuation)
// 🚀 在 Main 线程恢复用户状态机
continuation.resumeWith(result)
}
// 🎯 用户状态机在 Main 线程恢复
class UserStateMachine {
override fun invokeSuspend(result: Result<Any?>): Any? {
when (label) {
1 -> {
val data = result.getOrThrow() as String // "Network Data"
println("Got result: $data - Thread: ${Thread.currentThread().name}")
// 输出:Got result: Network Data - Thread: main
return Unit
}
}
}
}
🤔 问题 & 回答
Q1: withContext 如何实现无缝的上下文切换?
A1: 通过 DispatchedCoroutine 和调度器协作实现双向切换
🔄 双向切换机制
// 🎯 切换到目标上下文
withContext(Dispatchers.IO) {
// 1. 创建DispatchedCoroutine(IO上下文, 原始continuation)
// 2. 通过IO调度器派发到IO线程
// 3. 在IO线程执行block代码
performIOOperation()
}
// 4. block执行完成后,DispatchedCoroutine.afterResume()
// 5. 通过原始上下文的调度器派发回原始线程
// 6. 在原始线程继续执行后续代码
🧵 线程切换示例
suspend fun demonstrateContextSwitch() {
println("1. Main thread: ${Thread.currentThread().name}")
val result = withContext(Dispatchers.IO) {
println("2. IO thread: ${Thread.currentThread().name}")
delay(1000) // 模拟IO操作
"IO Result"
}
println("3. Back to main: ${Thread.currentThread().name}")
println("Result: $result")
}
// 输出示例:
// 1. Main thread: main
// 2. IO thread: DefaultDispatcher-worker-1
// 3. Back to main: main
// Result: IO Result
Q2: withContext 的性能优化机制是什么?
A2: 多层优化避免不必要的线程切换和对象创建
🚀 性能优化策略
// 🔍 1. 上下文相等性检查
suspend fun optimizationExample() {
withContext(Dispatchers.Main) { // 假设已经在Main线程
// newContext === oldContext 检查
// 发现相同,直接执行,无需切换
updateUI()
}
}
// ⚡ 2. 调度器级别优化
class MainDispatcher : CoroutineDispatcher() {
override fun isDispatchNeeded(context: CoroutineContext): Boolean {
// 🔍 检查当前是否已在主线程
return Looper.myLooper() != Looper.getMainLooper()
}
override fun dispatch(context: CoroutineContext, block: Runnable) {
if (isDispatchNeeded(context)) {
handler.post(block) // 需要切换才派发
} else {
block.run() // 直接执行,避免Handler开销
}
}
}
// 🏃 3. 快速路径执行
// 如果block立即完成(不包含挂起点),直接返回结果
suspend fun fastPath() {
val result = withContext(Dispatchers.Default) {
// 纯计算,无挂起点
42 * 2
}
// 可能直接返回84,无需实际线程切换
}
// 🔄 4. 延迟拦截优化
suspend fun delayedInterception() {
withContext(Dispatchers.IO) {
// 🏗️ createCoroutineUnintercepted 创建原始协程
// 🚀 立即尝试执行,如果无挂起点则直接完成
// ⏸️ 只有遇到挂起点时,才会调用 intercepted() 创建调度包装器
someOperation() // 如果这里不挂起,就不会创建 DispatchedContinuation
}
}
Q3: withContext 如何处理异常传播?
A3: 异常在上下文切换过程中正确传播,不会丢失
🛡️ 异常传播机制
suspend fun exceptionPropagation() {
try {
val result = withContext(Dispatchers.IO) {
// 🔥 在IO线程抛出异常
throw IOException("Network error")
}
} catch (e: IOException) {
// ✅ 异常正确传播到原始上下文
println("Caught in original context: ${e.message}")
}
}
// 🔄 异常传播流程
// 1. IO线程执行block时抛出IOException
// 2. DispatchedCoroutine.resumeWith(Result.failure(IOException))
// 3. afterResume()将异常包装为Result.failure
// 4. 派发回原始线程
// 5. 原始线程的状态机收到Result.failure
// 6. throwOnFailure(result)重新抛出异常
// 7. 用户代码的try-catch捕获异常
// 🚫 取消异常的特殊处理
suspend fun cancellationHandling() {
val job = launch {
try {
withContext(Dispatchers.IO) {
delay(5000) // 长时间操作
println("IO operation completed")
}
} catch (e: CancellationException) {
println("withContext was cancelled")
throw e // 重新抛出,不吞没取消异常
}
}
delay(1000)
job.cancel() // 取消会传播到withContext内部
}
// 🔗 异常处理器的作用
suspend fun exceptionHandlerExample() {
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
println("Uncaught exception: ${exception.message}")
}
launch(exceptionHandler) {
withContext(Dispatchers.IO) {
throw RuntimeException("Unhandled error")
// 🎯 异常会传播到launch的异常处理器
}
}
}
Q4: withContext 与 async/await 的区别?
A4: withContext 用于上下文切换,async/await 用于并发执行
📊 使用场景对比
// ✅ withContext - 上下文切换
suspend fun useWithContext() {
val userData = withContext(Dispatchers.IO) {
// 🧵 切换到IO线程执行
apiService.fetchUser()
}
// 🧵 自动切换回原始线程
updateUI(userData)
}
// ✅ async/await - 并发执行
suspend fun useAsync() {
val userDeferred = async(Dispatchers.IO) {
apiService.fetchUser()
}
val postsDeferred = async(Dispatchers.IO) {
apiService.fetchPosts()
}
// 🚀 并发等待结果
val user = userDeferred.await()
val posts = postsDeferred.await()
updateUI(user, posts)
}
// 🔄 性能对比
suspend fun performanceComparison() {
// ❌ 串行执行 - 总时间 = 2秒
val time1 = measureTimeMillis {
val user = withContext(Dispatchers.IO) {
delay(1000); "User"
}
val posts = withContext(Dispatchers.IO) {
delay(1000); "Posts"
}
}
// ✅ 并发执行 - 总时间 = 1秒
val time2 = measureTimeMillis {
val userDeferred = async(Dispatchers.IO) {
delay(1000); "User"
}
val postsDeferred = async(Dispatchers.IO) {
delay(1000); "Posts"
}
val user = userDeferred.await()
val posts = postsDeferred.await()
}
println("Serial: ${time1}ms, Concurrent: ${time2}ms")
}
// 🎯 选择原则
// withContext: 需要在特定线程执行某个操作
// async/await: 需要并发执行多个独立操作
Q5: intercepted() 何时被调用,为什么不在创建时就调用?
A5: intercepted() 采用延迟调用策略,只在真正需要调度时才创建包装器
🔍 intercepted() 调用时机详解
// 🎯 关键理解:延迟拦截 vs 立即拦截
suspend fun understandInterceptedTiming() {
println("=== Fast Path Example ===")
val fastResult = withContext(Dispatchers.IO) {
// 🚀 立即完成,无挂起点
42 * 2
}
// ✅ 这种情况可能永远不调用 intercepted()
println("=== Slow Path Example ===")
val slowResult = withContext(Dispatchers.IO) {
// ⏸️ 有挂起点
delay(100)
42 * 2
}
// 🔄 delay 导致挂起,恢复时会调用 intercepted()
}
// 📊 性能优化原理
class PerformanceAnalysis {
// ❌ 如果立即拦截(性能差)
fun immediateInterception() {
val continuation = createCoroutineUnintercepted(receiver, completion)
val intercepted = continuation.intercepted() // 立即创建包装器
// 问题:即使协程立即完成,也创建了不必要的 DispatchedContinuation
}
// ✅ 延迟拦截(性能好)
fun delayedInterception() {
val continuation = createCoroutineUnintercepted(receiver, completion)
val result = continuation.resumeWith(Unit)
if (result === COROUTINE_SUSPENDED) {
// 只有真正挂起时,后续恢复才会调用 intercepted()
}
// 优势:立即完成的协程避免了对象创建开销
}
}
// 🔄 实际调用链路
suspend fun interceptedCallChain() {
withContext(Dispatchers.IO) {
// 1. createCoroutineUnintercepted 创建原始状态机
// 2. resumeWith(Unit) 立即尝试执行
// 3. 如果遇到 delay()...
delay(1000)
// 4. delay 内部调用 suspendCancellableCoroutine
// 5. 协程挂起,等待定时器
// 6. 定时器到期,调用 continuation.resumeWith()
// 7. resumeWith() 内部调用 intercepted() 获取调度器
// 8. 通过调度器派发到目标线程
"Result"
}
}
// 🎯 三种主要调用场景
// 1️⃣ 挂起后恢复
suspend fun suspendResume() {
withContext(Dispatchers.IO) {
delay(100) // 挂起点
// 恢复时: continuation.resumeWith() -> intercepted() -> dispatch()
}
}
// 2️⃣ 异常传播
suspend fun exceptionPropagation() {
try {
withContext(Dispatchers.IO) {
throw Exception("Error")
}
} catch (e: Exception) {
// 异常传播: completion.resumeWith(failure) -> intercepted() -> dispatch()
}
}
// 3️⃣ 取消传播
suspend fun cancellationPropagation() {
val job = launch {
withContext(Dispatchers.IO) {
delay(Long.MAX_VALUE)
}
}
job.cancel()
// 取消传播: continuation.resumeWith(cancellation) -> intercepted() -> dispatch()
}
Q6: DispatchedCoroutine 如何实现双向上下文切换?
A6: 通过继承 ScopeCoroutine 和重写 afterResume() 实现自动回切
🔄 双向切换实现原理
// 🏗️ DispatchedCoroutine 的核心设计
internal class DispatchedCoroutine<in T>(
context: CoroutineContext, // 🎯 目标上下文
private val uCont: Continuation<T> // 🔙 原始continuation
) : ScopeCoroutine<T>(context, uCont) {
// 🎯 关键方法:执行完成后自动切换回原始上下文
override fun afterResume(state: Any?) {
// 🚀 快速路径:如果上下文相同,直接恢复
if (tryResume()) return
// 🧵 否则通过原始上下文的调度器派发回去
uCont.intercepted().resumeWith(recoverResult(state, uCont))
}
// 🔍 优化:检查是否可以立即恢复
private fun tryResume(): Boolean {
val state = _state.value
if (state is NotCompleted) return false
// 🎯 如果原始上下文和当前上下文相同,直接恢复
if (uCont.context === context) {
uCont.resumeWith(recoverResult(state, uCont))
return true
}
return false
}
}
// 📊 完整的双向切换流程
suspend fun demonstrateBidirectionalSwitch() {
println("1. 原始线程: ${Thread.currentThread().name}")
val result = withContext(Dispatchers.IO) {
println("2. 目标线程: ${Thread.currentThread().name}")
// 🔄 这里可能有多次挂起和恢复
delay(100)
println("3. 仍在目标线程: ${Thread.currentThread().name}")
delay(100)
println("4. 还是目标线程: ${Thread.currentThread().name}")
"IO Result"
}
// 🔙 DispatchedCoroutine.afterResume() 自动切换回原始线程
println("5. 回到原始线程: ${Thread.currentThread().name}")
println("Result: $result")
}
// 🎯 切换机制的关键点
class SwitchMechanism {
// 🚀 去程:原始 -> 目标
fun forwardSwitch() {
// 1. withContext 创建 DispatchedCoroutine(目标上下文, 原始continuation)
// 2. block.startCoroutineUninterceptedOrReturn(coroutine, coroutine)
// 3. 如果需要挂起,通过目标上下文的调度器派发到目标线程
// 4. 在目标线程执行用户代码
}
// 🔙 回程:目标 -> 原始
fun backwardSwitch() {
// 1. 用户代码执行完成,DispatchedCoroutine.resumeWith() 被调用
// 2. afterResume() 被触发
// 3. uCont.intercepted() 获取原始上下文的调度器
// 4. 通过原始调度器派发回原始线程
// 5. 在原始线程恢复执行
}
}
// 🔄 嵌套 withContext 的处理
suspend fun nestedWithContext() {
println("1. Main: ${Thread.currentThread().name}")
withContext(Dispatchers.IO) {
println("2. IO: ${Thread.currentThread().name}")
withContext(Dispatchers.Default) {
println("3. Default: ${Thread.currentThread().name}")
delay(100)
}
// 🔙 自动回到 IO 线程
println("4. Back to IO: ${Thread.currentThread().name}")
}
// 🔙 自动回到 Main 线程
println("5. Back to Main: ${Thread.currentThread().name}")
}
Q7: withContext 在什么情况下会直接返回,不进行线程切换?
A7: 多种优化场景避免不必要的线程切换
🚀 直接返回的优化场景
// 🔍 1. 上下文完全相同
suspend fun sameContextOptimization() {
// 假设当前已在 Dispatchers.IO 线程
withContext(Dispatchers.IO) {
// newContext === oldContext 检查通过
// 直接执行,无需创建 DispatchedCoroutine
performIOOperation()
}
}
// ⚡ 2. 调度器判断无需派发
suspend fun dispatchNotNeeded() {
// 在主线程调用
withContext(Dispatchers.Main.immediate) {
// isDispatchNeeded() 返回 false
// 直接在当前线程执行
updateUI()
}
}
// 🏃 3. 快速路径 - 无挂起点
suspend fun fastPathExecution() {
val result = withContext(Dispatchers.Default) {
// 纯计算,无挂起函数调用
val sum = (1..1000).sum()
sum * 2
}
// startCoroutineUninterceptedOrReturn 可能直接返回结果
// 避免了协程挂起和线程切换
}
// 📊 性能测试对比
suspend fun performanceTest() {
// ✅ 优化场景 - 几乎无开销
val time1 = measureTimeMillis {
repeat(10000) {
withContext(Dispatchers.Main) { // 假设已在主线程
// 直接执行,无线程切换
}
}
}
// 🐌 需要切换 - 有线程切换开销
val time2 = measureTimeMillis {
repeat(10000) {
withContext(Dispatchers.IO) {
// 需要线程切换
}
}
}
println("Same context: ${time1}ms, Different context: ${time2}ms")
}
// 🔍 判断逻辑详解
class OptimizationLogic {
fun contextEqualityCheck() {
val oldContext = uCont.context
val newContext = oldContext + context
// 🎯 引用相等性检查
if (newContext === oldContext) {
// 情况1: 传入的context为EmptyCoroutineContext
// 情况2: 传入的context元素与现有完全相同
// 情况3: 传入的context被现有context完全覆盖
return // 直接执行,无需切换
}
}
fun dispatcherOptimization() {
// 🧵 调度器级别的优化
override fun isDispatchNeeded(context: CoroutineContext): Boolean {
return when {
// 主线程调度器
this is MainCoroutineDispatcher ->
Looper.myLooper() != Looper.getMainLooper()
// 无限制调度器
this is Unconfined -> false
// 其他情况根据具体实现
else -> true
}
}
}
}
// 🎯 实际应用建议
suspend fun bestPractices() {
// ✅ 好的做法:明确上下文切换意图
suspend fun loadData() {
val data = withContext(Dispatchers.IO) {
// 明确需要IO线程
database.query()
}
withContext(Dispatchers.Main) {
// 明确需要主线程
updateUI(data)
}
}
// ❌ 避免:不必要的上下文切换
suspend fun inefficient() {
withContext(Dispatchers.Main) { // 如果已在主线程,这是多余的
withContext(Dispatchers.Main) { // 嵌套相同上下文
updateUI()
}
}
}
// ✅ 更好:检查当前上下文
suspend fun efficient() {
// 只在需要时切换
if (Looper.myLooper() != Looper.getMainLooper()) {
withContext(Dispatchers.Main) {
updateUI()
}
} else {
updateUI()
}
}
}
更多推荐

所有评论(0)