总结
代码中的所有协程代码(指 lambda 表达式中的代码)会被编译成一个 Continuation 对象(准确说是它的一个子类对象),该对象封装有状态机。
-
当协程开始执行时,会经过 interceptContinuation 方法,该方法会将 Continuation 对象封装成
DispatchedContinuation对象(它同时也是 Runnable 对象) -
接着会调用后者的
resumeCancellableWith方法,该方法内部会通过 dispatcher 进行线程切换。简单理解就是在一个线程池里执行 Runnable 对象,这个对象就是第一步中的 DispatchedContinuation 对象 -
在 DispatchedContinuation#run()方法中,会调用 DispatchedContinuation 内部封装的 Continuation 对象(也就是 lambda 代码生成的对象)的 resume() 方法,进而触发 lambda 的执行。到此,线程切换完成。
参考文章
前提
我们都知道协程切换线程可以通过 Dispatchers 来实现。先通过代码捋一下它们的继承关系
箭头标识处表示 dispatchers 来自于 ContinuationInterceptor,这个在分析切换线程时有用。
切到目标线程
协程中切换线程常用的有通过 launch 新启动一个协程,或者通过 withContext 直接切换协程运行所在的线程。两个方法最终都是殊途同归,都是执行下面的方法:
// Cancellable.kt
internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(
receiver: R, completion: Continuation<T>,
onCancellation: ((cause: Throwable) -> Unit)? = null
) =
runSafely(completion) {// runSafely 可以理解为直接执行第二个参数
// 只有在第二个参数运行过程中出异常时,才执行第一个参数
// 第一部分将 suspend 函数裹巴裹巴成一个对象,然后返回这个对象,
// 它是 ContinuationImpl 子类
createCoroutineUnintercepted(receiver, completion)
.intercepted() // 拦截器
.resumeCancellableWith(Result.success(Unit), onCancellation)
}
intercepted() 最终代码为:
public fun intercepted(): Continuation<Any?> =
intercepted
?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also { intercepted = it }
// 执行到此方法时。如果通过 newSingleThreadContext 创建的 context,那么 this 就是 ThreadPoolDispatcher
// continuation 就是上面说的裹巴着 suspend 函数的对象
public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
DispatchedContinuation(this, continuation)
resumeCancellableWith() 如下
public fun <T> Continuation<T>.resumeCancellableWith(
result: Result<T>,
onCancellation: ((cause: Throwable) -> Unit)? = null
): Unit = when (this) {
is DispatchedContinuation -> resumeCancellableWith(result, onCancellation) // 从上面逻辑看,走该分支
else -> resumeWith(result)
}
inline fun resumeCancellableWith(
result: Result<T>,
noinline onCancellation: ((cause: Throwable) -> Unit)?
) {
val state = result.toState(onCancellation)
// 该 dispatcher 就是上面说的 ThreadPoolDispatcher
// 判断是否需要切换线程
if (dispatcher.isDispatchNeeded(context)) {
_state = state
resumeMode = MODE_CANCELLABLE
// 该方法内部就是启用线程池,执行相应的方法
// 注意这里传入的是 this,所以最终执行的是 DispatchedContinuation#run 方法
dispatcher.dispatch(context, this)
} else {
executeUnconfined(state, MODE_CANCELLABLE) {
if (!resumeCancelled(state)) {
resumeUndispatchedWith(result)
}
}
}
}
上面的 dispatch() 最终会到 ContinuationImpl.kt 中的 BaseContinuationImpl#resumeWith() 方法,该方法才会真正调用 suspend 函数
切回原来线程
主要逻辑还是在 resumeWith() 中
// BaseContinuationImpl 中
public final override fun resumeWith(result: Result<Any?>) {
// This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
var current = this
var param = result
while (true) {
// Invoke "resume" debug probe on every resumed continuation, so that a debugging library infrastructure
// can precisely track what part of suspended callstack was already resumed
probeCoroutineResumed(current)
with(current) {
val completion = completion!! // fail fast when trying to resume continuation without completion
// 如果 invokeSuspend() 时抛异常了,最终会封装成一个 failure outcome
// 然后传递给 completion#resumeWith
val outcome: Result<Any?> =
try {
val outcome = invokeSuspend(param)
if (outcome === COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
releaseIntercepted() // this state machine instance is terminating
if (completion is BaseContinuationImpl) {
// unrolling recursion via loop
current = completion
param = outcome
} else {
// 这里的 completion 是 DispatchedCoroutine 对象,它继承处 AbstractCoroutine
// 后者本身实现了 Continuation 接口
// 因此,这里的 completion 不能当作 SuspendLambda 或者 ContinuationImpl 对象
// 而是要当作 Coroutine 对象
completion.resumeWith(outcome)
return
}
}
}
}
上面的 resumeWith() 底层是调用了 afterResume()
// DispatchedCoroutine
override fun afterResume(state: Any?) {
if (tryResume()) return
// uCont 指父协程,是 SuspendLambda 对象
// 这里就跟上面一样,通过 intercepted() 切换线程了
uCont.intercepted().resumeCancellableWith(recoverResult(state, uCont))
}
异常
自己的协程代码,最终会被编译到 invokeSuspend() 方法中。该方法内部会通过状态机处理不同挂起点后的恢复。invokeSuspend() 由 BaseContinuationImpl#resumeWith() 调用,内部有如下代码
try {
val outcome = invokeSuspend(param)
if (outcome === COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
// 直接 catch Throwable,所以协程代码无论出现何种异常都不会直接崩溃
Result.failure(exception)
}
当代码中出现异常时,会封装成 Result,然后不断向父协程传递。传递过程就是不断地进行方法调用,异常会被封装成参数,整个过程异常不会被重新抛出,所以使用 try-catch 无效。当我们协程代码出现异常时,直到最顶级做域、协程时都不会被重新抛出(正常情况下),也就是 try-catch {launch{}} 失效的原因
异常往上传播时,主要是 Coroutine#childCancelled() 方法,该方法返回 true 表示父协程处理了异常,否则表示未处理。
childCanclled() 最初的实现在 JobSupport 中,AbstractCoroutine 继承 JobSupport,所有每一个协程都有默认实现:
public open fun childCancelled(cause: Throwable): Boolean {
// 如果是取消,返回 true,表示父协程处理了异常
// 同时父协程未将异常往上传输,也没有取消所有子协程
if (cause is CancellationException) return true
// 否则调用 cancelImpl
// 该方法的分析见链接,总之:1. 取消自己所有的子协程;
// 2. 调用父协程的 childCancelled,也就是将取消传递到父协程
return cancelImpl(cause) && handlesException
}
CancellationException
从上面代码可以看出 CancellationException 的处理流程:父协程直接忽略该异常,整个异常传播流程结束
SupervisorJob
阻止异常往上传播,将异常交给子协程处理
SupervisorJob 的实现类是 SupervisorJobImpl,它重写了 childCancelled() 方法
override fun childCancelled(cause: Throwable): Boolean = false
异常的最终处理
上面说过,子协程通过 childCancelled() 将异常传递给父协程,该方法默认实现会调用 cancelImpl(),后者经过不断调用最终到 JobSupport#finalizeFinishingState()。该方法有如下代码
// cancelParent() 会调用到父协程的 childCanclled(),如果返回 true,表示父协程处理, || 后面就不会执行
// 否则执行后面
val handled = cancelParent(finalException) || handleJobException(finalException)
从上面更容易理解 CancellationException 与 SupervisorJob 的实现效果。
对于 handleJobException() 默认实现是返回 false。只有 StandaloneCoroutine 重写了该方法,我们通过 launch{} 启动协程就是 StandaloneCoroutine 的实例。它的处理如下:
public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
// Invoke an exception handler from the context if present
try {
// 调用 CoroutineExceptionHandler 进行处理
// 处理完后直接返回
context[CoroutineExceptionHandler]?.let {
it.handleException(context, exception)
return
}
} catch (t: Throwable) {
handleCoroutineExceptionImpl(context, handlerException(exception, t))
return
}
// If a handler is not present in the context or an exception was thrown, fallback to the global handler
// 没有设置 handler 的话,就执行 handleCoroutineExceptionImpl
// 见下面
handleCoroutineExceptionImpl(context, exception)
}
// 通过 spi 机制加载自定义异常处理
private val handlers: List<CoroutineExceptionHandler> = ServiceLoader.load(
CoroutineExceptionHandler::class.java,
CoroutineExceptionHandler::class.java.classLoader
).iterator().asSequence().toList()
internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) {
// use additional extension handlers
// 使用 spi 加载自定义异常处理类
for (handler in handlers) {
try {
handler.handleException(context, exception)
} catch (t: Throwable) {
// Use thread's handler if custom handler failed to handle exception
val currentThread = Thread.currentThread()
currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, handlerException(exception, t))
}
}
// use thread's handler
// 如果都没有,就调用当前线程的 uncaughtExceptionHandler
val currentThread = Thread.currentThread()
currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)
}
上面就是协程中关于异常处理的整个流程。
异常处理的关键点
-
异常处理是通过方法调用形式往上传播,整个传播过程中不会的 throw。所以在 launch 外使用 try-catch 无效 -
异常最终由最顶层的 scope/协程进行处理,所以中途设置的 CoroutineExceptionHandler 无效 -
coroutineScope 是一个特殊的 scope,它收到异常后会重新抛出,所以 try-catch 可以捕获到 coroutineScope 中发生的异常 -
supervisorScope 内部使用 SupervisorJob,异常传递到它时会被直接打回,交由其直接子协程处理- 由于其直接子协程有机会处理异常,所以子协程设置的 CoroutineExceptionHandler 会生效
-
async 启动的协程需要处理两处异常:await() 会重新抛出异常,所以需要 try-catch;协程发生异常时会不断上报,所以需要在根协程/scope 处再处理一次异常- async 并没有重写 handleJobException(),所以 async 做为根协程时,如果子协程出现异常,并不会产生任何输出。比如下面代码
// 使用 async 不会有任何信息输出 // 换成 launch 就会看到报错信息 CoroutineScope(Dispatchers.IO). async { launch { 1 / 0 } } -
withContext 启动的协程在收到异常时会重新抛了,这与 coroutineScope 类似,比如下面代码try { withContext(Dispatchers.IO) { 1 / 0 } } catch (e: Throwable) { println("aaa") // 可以收到异常。应用不会崩 }
线程安全
协程主要用于多线程环境,所以可能会出现线程安全问题
即使为协程指定了相同的调度器,协程也不一定会运行在同一个线程中,因为调度器内容使用的是线程池同一个协程,恢复前后的线程也可能不一样。当一个协程从挂起函数处恢复时,执行它剩余代码的线程有可能已经发生了变化。所以一个协程内部也不线程安全