文章目录
前言:
前两篇文章我们介绍了协程的基础知识和上下文:
这篇文章我们将介绍协程的取消和异常。
1.协程的取消
前面我们介绍到launch()函数返回的是一个Job对象。想要取消一个正在运行的协程也比较简单,我们只需要调用Job的cancel()方法就可以取消一个协程,如下代码示例:
fun main() {
val job = CoroutineScope(Dispatchers.IO).launch {
repeat(3) {
println("repeat: $it, isActive = $isActive")
}
}
job.cancel()
runBlocking { delay(100) }
}
// 输出
repeat: 0, isActive = false
repeat: 1, isActive = false
repeat: 2, isActive = false
从输出结果来看,cancel()方法并没有将协程正确的取消。这里在runBlocking()函数中delay100毫秒来保证JVM的存活。Job中的isActive字段已经是false了,但是repeat()函数中的代码还是成功执行了。cancel()方法只是更改了Job的状态,而并不能终止正在运行的协程。这就好比线程中的interrputed()方法。
就像官网介绍的那样,协程的取消是协作的,我们需要判断协程的状态来完成协程所谓真正意义上的取消。当我们在launch()函数中添加isActive字段时,我们的取消才是有意义的。如下代码示例:
fun main() {
val job = CoroutineScope(Dispatchers.IO).launch {
repeat(3) {
if (!isActive) return@launch
println("repeat: $it, isActive = $isActive")
}
}
job.cancel()
runBlocking { delay(100) }
}
// 无任何输出
2.父协程和子协程
默认情况下,在一个协程体内直接创建协程,二者会产生父子关系
// 父Job
CoroutineScope(Dispatchers.Default).launch {
// 子job1
launch { }
// 子job2
launch { }
}
launch()函数:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
StandaloneCoroutine也是一个Job,其继承关系如下:
--CoroutineContext
--Element
--Job
--JobSupport
--AbstractCoroutine
--StandaloneCoroutine
在抽象类AbstractCoroutine的init块中会调用其父类JobSupport中的initParentJob()方法:
public abstract class AbstractCoroutine<in T>(
parentContext: CoroutineContext,
initParentJob: Boolean,
active: Boolean
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
...
init {
if (initParentJob) initParentJob(parentContext[Job])
}
...
}
protected fun initParentJob(parent: Job?) {
assert { parentHandle == null }
// 根Job,没有parent 将NonDisposableHandle赋值给parentHandle
if (parent == null) {
parentHandle = NonDisposableHandle
return
}
parent.start() // 确保父Job已启动
val handle = parent.attachChild(this)
// 将attachChild方法的返回值赋值给parentHandle
parentHandle = handle
// 现在检查我们的状态after_registering(请参阅tryFinalizeSimpleState操作顺序)
if (isCompleted) {
handle.dispose()
parentHandle = NonDisposableHandle // 释放它以防万一,以帮助GC
}
}
attachChild()函数:
public final override fun attachChild(child: ChildJob): ChildHandle {
return invokeOnCompletion(
onCancelling = true,
handler = ChildHandleNode(child).asHandler) as ChildHandle
}
invokeOnCompletion()方法返回的是一个ChildHandle,由于这里涉及到类的继承关系有点复杂,为了方便阅读,这里把JobNode中的类继承关系整理了一下:
invokeOnComletion()方法主要是将子Job添加到父Job的节点中,还会将Job的状态更改为Incomplete。这里可以理解为Job处于活跃状态。(注意:LockFreeLinkedListNode是一个双向链表的数据结构)
public final override fun invokeOnCompletion(
onCancelling: Boolean,
invokeImmediately: Boolean,
handler: CompletionHandler
): DisposableHandle {
// 根据hander类型和onCancelling创建JobNode对象
val node: JobNode = makeNode(handler, onCancelling)
loopOnState { state ->
when (state) {
is Empty -> { // EMPTY_X状态--没有完成处理程序
if (state.isActive) {
// 将Job的状态更改为 InComplete
if (_state.compareAndSet(state, node)) return node
} else
promoteEmptyToNodeList(state) // 这样我们就可以为非活动协同程序添加监听器
}
is Incomplete -> {
val list = state.list
if (list == null) { // 如果list为空,就创建一个list,并更改当前job的状态
promoteSingleToNodeList(state as JobNode)
} else {
...
// 将子节点添加到父节点中
if (addLastAtomic(state, list, node)) return node
...
}
}
else -> { // is complete
// 父Job已经处于完成状态了,取消他的子Job
if (invokeImmediately) handler.invokeIt((state as? CompletedExceptionally)?.cause)
return NonDisposableHandle
}
}
}
}
首先我们来看下makeNode()方法:
private fun makeNode(handler: CompletionHandler, onCancelling: Boolean): JobNode {
val node = if (onCancelling) {
(handler as? JobCancellingNode)
?: InvokeOnCancelling(handler)
} else {
(handler as? JobNode)
?.also { assert { it !is JobCancellingNode } }
?: InvokeOnCompletion(handler)
}
node.job = this
return node
}
这里我们的onCancelling传的是true,handler是一个ChildHandleNode,同时也是JobCancellingNode的子类,所以这里我们的node变量就是一个JobCancellingNode类型,最后我们将当前的JobSupport赋值给node.job,然后返回node。(注意:这里的Job就是我们当前Job的父Job)。
internal abstract class JobNode : CompletionHandlerBase(), DisposableHandle, Incomplete {
lateinit var job: JobSupport
...
}
internal abstract class JobCancellingNode : JobNode()
internal class ChildHandleNode(
@JvmField val childJob: ChildJob
) : JobCancellingNode(), ChildHandle {
override val parent: Job get() = job
override fun invoke(cause: Throwable?) = childJob.parentCancelled(job)
override fun childCancelled(cause: Throwable): Boolean = job.childCancelled(cause)
}
可以看到最终这里我们把job赋值了parent。当子Job取消的时候,我们使用childCancelled()方法来决定要不要取消父Job,当父Job取消的时候我们调用parentCancelled()方法来取消它的子Job。invoke方法由CompletionHandler覆盖而来,由上面的继承关系我们可以看到JobNode继承自CompletionHandlerBase
public typealias CompletionHandler = (cause: Throwable?) -> Unit
internal actual abstract class CompletionHandlerBase actual constructor() : LockFreeLinkedListNode(), CompletionHandler {
actual abstract override fun invoke(cause: Throwable?)
}
// CompletionHandler的扩展函数
internal actual inline fun CompletionHandler.invokeIt(cause: Throwable?) = invoke(cause)
loopOnState()方法中有一个while循环来查询我们Job的状态:
private inline fun loopOnState(block: (Any?) -> Unit): Nothing {
while (true) {
block(state)
}
}
private val SEALED = Symbol("SEALED")
private val EMPTY_NEW = Empty(false)
private val EMPTY_ACTIVE = Empty(true)
// 由于这里的active是true,所以Job的默认状态是 EMPTY_ACTIVE
private val _state = atomic<Any?>(if (active) EMPTY_ACTIVE else EMPTY_NEW)
internal val state: Any? get() {
_state.loop { state -> // 帮助程序循环状态(完成正在进行的原子操作)
if (state !is OpDescriptor) return state
state.perform(this)
}
}
由此可以看出我们Job的初始状态是EMPTY_ACTIVE。
3.Job.cancel()
JobSupport实现了Job接口,并重写了Job中的cancel()方法:
public fun cancel(cause: CancellationException? = null)
public override fun cancel(cause: CancellationException?) {
// 如果cause为空,则创建默认的异常
cancelInternal(cause ?: defaultCancellationException())
}
internal inline fun defaultCancellationException(message: String? = null, cause: Throwable? = null) =
// 如果message为空,则创建默认的message
JobCancellationException(message ?: cancellationExceptionMessage(), cause, this)
public open fun cancelInternal(cause: Throwable) {
cancelImpl(cause)
}
cancelImpl()函数:
// job内部调用cancel方法
public open fun cancelInternal(cause: Throwable) {
cancelImpl(cause)
}
// 父Job取消,通知取消其子Job
public final override fun parentCancelled(parentJob: ParentJob) {
cancelImpl(parentJob)
}
// 子Job取消,通知取消其父Job
public open fun childCancelled(cause: Throwable): Boolean {
if (cause is CancellationException) return true
return cancelImpl(cause) && handlesException
}
// 注意:这里的cause之所以声明为Any,是因为会传入不同类型的参数,如果由上面的
// cancelInternal()、childCancelled()方法调用则是一个Throwable。
// 如果由parentCancelled调用则是ParentJob
internal fun cancelImpl(cause: Any?): Boolean
// finalState的默认状态为COMPLETING_ALREADY
var finalState: Any? = COMPLETING_ALREADY
// 根Job中onCancelComplete为true,详见JobImpl类
if (onCancelComplete) {
// 确保它正在完成,如果cancelMakeCompleting 返回状态,则表示它已完成并记录了异常
finalState = cancelMakeCompleting(cause)
// 等待子Job完成
if (finalState === COMPLETING_WAITING_CHILDREN) return true
}
if (finalState === COMPLETING_ALREADY) {
finalState = makeCancelling(cause)
}
return when {
finalState === COMPLETING_ALREADY -> true
finalState === COMPLETING_WAITING_CHILDREN -> true
finalState === TOO_LATE_TO_CANCEL -> false
else -> {
afterCompletion(finalState)
true
}
}
}
onCancelComplete是JubSupport中声明的一个可覆盖的布尔类型的属性:
internal open val onCancelComplete: Boolean get() = false
private class CompletableDeferredImpl<T>( parent: Job?) : JobSupport(true), CompletableDeferred<T> {
...
override val onCancelComplete get() = true
...
}
internal open class JobImpl(parent: Job?) : JobSupport(true), CompletableJob {
...
override val onCancelComplete get() = true
...
}
而我们在创建协程上下文对象的时候所使用的Job都是JobImpl,所以当onCancelComplete为true的时候,我们可以理解为我们在取消一个根Job或某个子Job因为产生了JobCancellationException以外的异常来取消它的父Job。我们接着往下看cancelMakeCompleting()方法的实现:
// cause同上面的cancelImpl()方法
// 它包含一个循环,从不返回COMPLETING_RETRY,可以返回:
// COMPLETING_ALREADY -- 如果已经完成或正在完成
// COMPLETING_WAITING_CHILDREN -- 如果开始等待孩子
// final state -- 完成后调用afterCompletion
private fun cancelMakeCompleting(cause: Any?): Any? {
loopOnState { state ->
if (state !is Incomplete || state is Finishing && state.isCompleting) {
// 已经完成,直接返回
return COMPLETING_ALREADY
}
// 创建一个CompletedExceptionally对象,并且cause为JobCancellationException
val proposedUpdate = CompletedExceptionally(createCauseException(cause))
val finalState = tryMakeCompleting(state, proposedUpdate)
if (finalState !== COMPLETING_RETRY) return finalState
}
}
private fun createCauseException(cause: Any?): Throwable = when (cause) {
is Throwable? -> cause ?: defaultCancellationException()
// 如果cause是ParentJob,则调用getChildJobCancellationCause()
else -> (cause as ParentJob).getChildJobCancellationCause()
}
override fun getChildJobCancellationCause(): CancellationException {
// 确定此作业的根本取消原因(为什么要取消其子项?)
val state = this.state
val rootCause = when (state) {
is Finishing -> state.rootCause
is CompletedExceptionally -> state.cause
is Incomplete -> error("Cannot be cancelling child in this state: $state")
else -> null // 在正常完成时使用以下代码创建异常
}
return (rootCause as? CancellationException) ?: JobCancellationException("Parent job is ${stateString(state)}", rootCause, this)
}
接着会调用到tryMakeCompleting()方法,当onCancelComplete为false时,它会调用到makeCancelling()方法,这里面也会涉及到tryMakeCompleting()的方法的调用,包括协程的异常过程也会调用到该方法,下面我们先来看下Job的异常。
4.Job抛出异常
在BaseContinuatonImpl类中协程已经将异常捕获,并且会将异常作为结果值传递给resumeWith()方法:
internal abstract class BaseContinuationImpl(
public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
public final override fun resumeWith(result: Result<Any?>) {
var current = this
var param = result
while (true) {
probeCoroutineResumed(current)
with(current) {
val completion = completion!!
val outcome: Result<Any?> =
try {
val outcome = invokeSuspend(param)
if (outcome === COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
releaseIntercepted()
if (completion is BaseContinuationImpl) {
current = completion
param = outcome
} else {
completion.resumeWith(outcome)
return
}
}
}
}
}
然后调用AbstratCoroutine中的resumeWith()方法:
// 使用具有指定结果的协同程序完成此操作的执行。
public final override fun resumeWith(result: Result<T>) {
val state = makeCompletingOnce(result.toState())
if (state === COMPLETING_WAITING_CHILDREN) return
afterResume(state)
}
protected open fun afterResume(state: Any?): Unit = afterCompletion(state)
internal fun makeCompletingOnce(proposedUpdate: Any?): Any? {
loopOnState { state ->
val finalState = tryMakeCompleting(state, proposedUpdate)
when {
finalState === COMPLETING_ALREADY ->
throw IllegalStateException(
"Job $this is already complete or completing, " +
"but is being completed with $proposedUpdate", proposedUpdate.exceptionOrNull
)
finalState === COMPLETING_RETRY -> return@loopOnState
else -> return finalState // COMPLENG_WAITING_CHILDREN或最终状态
}
}
}
tryMakeCompleting()方法:
// 返回 COMPLETING 符号或最终状态之一:
// COMPLETING_ALREADY -- 当已经完成或完成时
// COMPLETING_RETRY -- 因干扰而需要重试时
// COMPLETING_WAITING_CHILDREN -- 完成并正在等待孩子
// final state -- 完成后,用于调用 afterCompletion
private fun tryMakeCompleting(state: Any?, proposedUpdate: Any?): Any? {
if (state !is Incomplete)
return COMPLETING_ALREADY
/*
* 快速路径--没有子级等待&&简单状态(无列表)&&不取消=>可以立即完成
* 取消(失败)总是必须经过Finishing状态才能序列化异常处理。
* 否则,可能会在(已完成状态->已处理的异常和新附加的子级/联接)之间发生竞争
* 这可能会错过未处理的异常。
*/
if ((state is Empty || state is JobNode) && state !is ChildHandleNode && proposedUpdate !is CompletedExceptionally) {
if (tryFinalizeSimpleState(state, proposedUpdate)) {
// Completed successfully on fast path -- return updated state
return proposedUpdate
}
return COMPLETING_RETRY
}
// 单独的慢速路径功能可简化分析
return tryMakeCompletingSlowPath(state, proposedUpdate)
}
tryMakeCompletingSlowPath()方法:
// 返回 COMPLETING 符号或最终状态之一
// COMPLETING_ALREADY -- 当已经完成或完成时
// COMPLETING_RETRY -- 因干扰而需要重试时
// COMPLETING_WAITING_CHILDREN -- 完成并正在等待孩子
// final state -- 完成后,用于调用 afterCompletion
private fun tryMakeCompletingSlowPath(state: Incomplete, proposedUpdate: Any?): Any? {
// 获取状态的列表或提升到列表以正确操作子列表
val list = getOrPromoteCancellingList(state) ?: return COMPLETING_RETRY
// 如果我们还没有进入完成状态,则升级到完成状态
// 这种提升必须是状态更改的原子w.r.t,这样还没有激活的协同程序
// 原子过渡到完成状态
val finishing = state as? Finishing ?: Finishing(list, false, null)
// 必须将更新同步到完成状态
var notifyRootCause: Throwable? = null
synchronized(finishing) {
// 检查此状态是否已完成
if (finishing.isCompleting) return COMPLETING_ALREADY
// 标记为完成
finishing.isCompleting = true
// 如果我们需要提升到finishing,那么在这里进行原子化操作。
// 我们在保持锁定的同时尽可能早地完成。这样可以确保我们尽快cancelImpl
// (如果其他人更快的话),我们会尽快同步这个完成锁上的所有线程。
if (finishing !== state) {
if (!_state.compareAndSet(state, finishing)) return COMPLETING_RETRY
}
##重要不变:只有一个线程(设置为完成)可以超过这一点
assert { !finishing.isSealed } // 无法密封
// 将新提议的异常添加到完成状态
val wasCancelling = finishing.isCancelling
(proposedUpdate as? CompletedExceptionally)?.let { finishing.addExceptionLocked(it.cause) }
// 如果只是取消-->必须处理取消通知
notifyRootCause = finishing.rootCause.takeIf { !wasCancelling }
}
// 在这里处理取消通知--它取消了所有的孩子,我们开始等待他们(原文如此!!)
notifyRootCause?.let { notifyCancelling(list, it) }
// now wait for children
val child = firstChild(state)
if (child != null && tryWaitForChild(finishing, child, proposedUpdate))
return COMPLETING_WAITING_CHILDREN
// 否则——我们就没有孩子了(都已经取消了?)
return finalizeFinishingState(finishing, proposedUpdate)
}
该方法主要的作用是将当前Job的状态置为Finishing,发出协程被取消的通知。然后判断子Job是否已经完成,如果没有完成就等待它的子Job完成,否则调用finalizeFinishingState()。
5.关键类Finishing解析
// 正在完成和取消状态,
// 所有更新都由synchronized(this)保护,读取是不稳定的
private class Finishing(
override val list: NodeList,
isCompleting: Boolean,
rootCause: Throwable?
) : SynchronizedObject(), Incomplete {
private val _isCompleting = atomic(isCompleting)
var isCompleting: Boolean
get() = _isCompleting.value
set(value) { _isCompleting.value = value }
private val _rootCause = atomic(rootCause)
var rootCause: Throwable? // 注:即使在密封的情况下,根本原因也会保留
get() = _rootCause.value
set(value) { _rootCause.value = value }
private val _exceptionsHolder = atomic<Any?>(null)
private var exceptionsHolder: Any? // 包含null | Throwable | ArrayList | SEALED
get() = _exceptionsHolder.value
set(value) { _exceptionsHolder.value = value }
// 注意:密封时无法修改
val isSealed: Boolean get() = exceptionsHolder === SEALED
val isCancelling: Boolean get() = rootCause != null
override val isActive: Boolean get() = rootCause == null // !isCancelling
// 密封当前状态并返回异常列表
// 由“synchronized(this)”保护`
fun sealLocked(proposedException: Throwable?): List<Throwable> {
val list = when(val eh = exceptionsHolder) { // volatile read
null -> allocateList()
is Throwable -> allocateList().also { it.add(eh) }
is ArrayList<*> -> eh as ArrayList<Throwable>
else -> error("State is $eh") // 已经密封——不可能发生
}
val rootCause = this.rootCause // 原子读取
rootCause?.let { list.add(0, it) } // 注意--根本原因从头开始
if (proposedException != null && proposedException != rootCause) list.add(proposedException)
exceptionsHolder = SEALED
return list
}
// 由“synchronized(this)”保护`
fun addExceptionLocked(exception: Throwable) {
val rootCause = this.rootCause // 原子读取
if (rootCause == null) {
this.rootCause = exception
return
}
if (exception === rootCause) return // 无需做任何操作
when (val eh = exceptionsHolder) { // 原子读取
null -> exceptionsHolder = exception
is Throwable -> {
if (exception === eh) return // 无需做任何操作
exceptionsHolder = allocateList().apply {
add(eh)
add(exception)
}
}
is ArrayList<*> -> (eh as ArrayList<Throwable>).add(exception)
else -> error("State is $eh") // 已经密封——不可能发生
}
}
private fun allocateList() = ArrayList<Throwable>(4)
override fun toString(): String =
"Finishing[cancelling=$isCancelling, completing=$isCompleting, rootCause=$rootCause, exceptions=$exceptionsHolder, list=$list]"
}
仔细一看其实代码量并不多,主要声明了isCompleting、rootCause、exceptionsHolder、isSealed、isCancelling属性,同时覆盖了isActive属性。两个比较重要的方法:
fun sealLocked(proposedException: Throwable?): List<Throwable> { }
fun addExceptionLocked(exception: Throwable)
两者总是一前一后的调用,从而对异常进行保存以及状态的判断和流转。
private fun allocateList() = ArrayList<Throwable>(4)
allocateList()方法则是声明一个容量为4的ArrayList来存储Throwable。仔细分析这两个方法我们会发现,rootCase是在第一次调用addExceptionLocked()被赋值的,如果后面再次调用则是通过exceptionsHolder来保存的。因为父Job在等待子Job的时候,可能存在多个子Job都发生异常的情况,所以这里给保存异常的容量也做了一个升级变化。
- 发生一次直接用一个属性rootCase来保存
- 发生两次用两个属性rootCase和exceptionsHolder分别来保存
- 发生三次直接用ArrayList来保存
// 注意:这里是1、2、3是假设情况每个子Job发生的异常都是不同的情况
- 第一次调用addExceptionLocked():保存rootCause,exceptionsHolder此时为null
- 第二次调用addExceptionLocked():因为一个Job可能有多个子Job,父Job在等待子Job的时候可能会存在不止一个子Job发生JobCancellationException以外的异常,当异常不同于第一次调用时发生的异常,我们就将异常使用属性exceptionsHolder来保存
- 第三次调用addExceptionLocked():当这里的异常和rootCause、exceptionsHolder都不相等,那么索性创建一个ArrayList,把这第二次和第三次生的异常保存下来
从而这也对应了我们在接下来将在finalizeFinishingState()中调用finish.sealLocked()方法,总是取第一次发生的异常,然后将exceptionsHolder的状态置为SEALED。
关键代码块解析:
synchronized(finishing) {
...
val wasCancelling = finishing.isCancelling
(proposedUpdate as? CompletedExceptionally)?.let { finishing.addExceptionLocked(it.cause) }
notifyRootCause = finishing.rootCause.takeIf { !wasCancelling }
}
notifyRootCause?.let { notifyCancelling(list, it) }
val isCancelling: Boolean get() = rootCause != null
finishing.isCancelling属性的get()方法是通过rootCause不为null来判断的,也是说在调用完一次finishing.addExceptionLocked()方法后,它就会返回true。这会影响到notifyRootCause的赋值,以及notifyCancelling()方法是否会执行。当多个子Job发生异常调用parentCancelled()方法来取消父Job的时候,notifyCancelling()方法不会被重复调用。
// finishing.rootCause.takeIf会返回 rootCause
notifyRootCause?.let { notifyCancelling(list, it) }
private fun notifyCancelling(list: NodeList, cause: Throwable) {
// 先取消子Job
onCancelling(cause)
notifyHandlers<JobCancellingNode>(list, cause)
// 然后取消父Job
cancelParent(cause) // 暂时取消——如果没有父Job也没关系
}
private inline fun <reified T: JobNode> notifyHandlers(list: NodeList, cause: Throwable?) {
var exception: Throwable? = null
list.forEach<T> { node ->
try {
node.invoke(cause)
} catch (ex: Throwable) {
exception?.apply { addSuppressedThrowable(ex) } ?: run {
exception = CompletionHandlerException("Exception in completion handler $node for $this", ex)
}
}
}
exception?.let { handleOnCompletionException(it) }
}
到这里就比较清楚了,上面我们已经介绍了ChildHandleNode中的invoke方法:
internal class ChildHandleNode(
@JvmField val childJob: ChildJob
) : JobCancellingNode(), ChildHandle {
...
override fun invoke(cause: Throwable?) = childJob.parentCancelled(job)
...
}
父Job取消,使用for循环来取消所有的子Job,然后再逐级向上传递。是否取消其父Job主要受两个因素影响:
public open fun childCancelled(cause: Throwable): Boolean {
if (cause is CancellationException) return true
return cancelImpl(cause) && handlesException
}
一个因素是cause是CancellationException不会取消。另一个因素就是我们是否重写了childCancelled()方法,例如我们的常见的:顶层函数SupervisorJob()和 supervisorScope(),他们在其内部实现中所创建的Job都会重写childCancelled()方法,并将其置为false。这样当子Job抛异常时不会在去取消它的父Job。其代码如下:
public suspend fun <R> supervisorScope(block: suspend CoroutineScope.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return suspendCoroutineUninterceptedOrReturn { uCont ->
val coroutine = SupervisorCoroutine(uCont.context, uCont)
coroutine.startUndispatchedOrReturn(coroutine, block)
}
}
private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {
override fun childCancelled(cause: Throwable): Boolean = false
}
private class SupervisorCoroutine<in T>(
context: CoroutineContext,
uCont: Continuation<T>
) : ScopeCoroutine<T>(context, uCont) {
override fun childCancelled(cause: Throwable): Boolean = false
}
接着就是调用finalizeFinishingState()方法,也是Job状态流转的最后一个方法了。
// 完成 Finishing -> Completed(终端状态)转换。
// 重要不变性:只有一个线程可以同时调用此方法。
// 返回创建并更新为的最终状态
private fun finalizeFinishingState(state: Finishing, proposedUpdate: Any?): Any? {
//注意:建议的状态可以是Incomplete,例如
// async{something.invokeOnCompletion{}
// <-返回在引擎盖下实现Incomplete的句柄}
assert { this.state === state } // 一致性检查--它不能更改
assert { !state.isSealed } // 一致性检查--还不能密封
assert { state.isCompleting } // 一致性检查--必须标记为完成
val proposedException = (proposedUpdate as? CompletedExceptionally)?.cause
// 创建最后一个异常并封存状态,以便不再添加异常
val wasCancelling: Boolean
val finalException = synchronized(state) {
wasCancelling = state.isCancelling
val exceptions = state.sealLocked(proposedException)
val finalCause = getFinalRootCause(state, exceptions) //总是取第一个
if (finalCause != null) addSuppressedExceptions(finalCause, exceptions)
finalCause
}
// 创建最终状态对象
val finalState = when {
// 没有被取消(没有例外)->使用建议的更新值
finalException == null -> proposedUpdate
// 当我们可以在取消时按原样使用proposeUpdate对象时进行小型优化
finalException === proposedException -> proposedUpdate
// 取消的作业最终状态
else -> CompletedExceptionally(finalException)
}
// 现在处理最后一个异常
if (finalException != null) {
val handled = cancelParent(finalException) || handleJobException(finalException)
if (handled) (finalState as CompletedExceptionally).makeHandled()
}
// 处理作业状态之前的最终状态的状态更新
// 以避免出现外部观察者可能看到作业处于最终状态,但尚未处理异常的情况。
if (!wasCancelling) onCancelling(finalException)
onCompletionInternal(finalState)
// 然后CAS进入完成状态->必须成功
val casSuccess = _state.compareAndSet(state, finalState.boxIncomplete())
assert { casSuccess }
// 并处理所有完成后的操作
completeStateFinalization(state, finalState)
return finalState
}
关键代码块:
if (finalException != null) {
// 默认取消父协程,如果异常不是JobCancellationException则分发异常
val handled = cancelParent(finalException) || handleJobException(finalException)
if (handled) (finalState as CompletedExceptionally).makeHandled()
}
6.cancelParent方法()
/**
* 取消作业时调用的方法,以可能将取消传播到父级。
* 如果父级负责处理异常,则返回“true”,否则返回“false”。
*
* 不变量:对于[CancellationException]的实例,从不返回“false”,否则为此类异常
* 可能泄漏到[CoroutineExceptionHandler]。
*/
private fun cancelParent(cause: Throwable): Boolean {
// 是作用域的协程--不传播,将重新抛出
if (isScopedCoroutine) return true
/* CancellationException被认为是“正常”的,当子级生成时,父级通常不会被取消。
* 这允许父级取消其子级(正常情况下)而不被取消,除非
* child在其完成过程中崩溃并产生一些其他异常。
*/
val isCancellation = cause is CancellationException
val parent = parentHandle
// 无父Job--忽略CancellationException,报告其他异常。
if (parent === null || parent === NonDisposableHandle) {
return isCancellation
}
// 取消父Job,但不要忘记检查取消
return parent.childCancelled(cause) || isCancellation
}
如果这里的cause是CancellationException,cancelParent()方法就会直接返回true,所以不会走到handleJobException()方法,这也是我们常说CancellationException异常会被协程内部消化,不会当做异常来处理。
接着我们来看下这行代码:
// 是作用域的协程--不传播,将重新抛出
if (isScopedCoroutine) return true
/**
* 对于作用域协同程序,返回“true”。
* 作用域协同程序是在封闭作用域内按顺序执行的协同程序,没有任何并发性。
* 作用域协同程序总是处理内部发生的任何异常——它们只是将其重新抛出到封闭作用域。
* 作用域协程的示例有“coroutineScope”、“withTimeout”和“runBlocking”。
*/
protected open val isScopedCoroutine: Boolean get() = false
关于这个属性的定义注释写的也比较清楚,如挂起函数coroutineScope()、顶层函数runBlocking()其内部创建的Job都将该属性覆盖成了true。
在runBlocking()中:
public actual fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T {
...
val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop)
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
return coroutine.joinBlocking()
}
private class BlockingCoroutine<T>(
parentContext: CoroutineContext,
private val blockedThread: Thread,
private val eventLoop: EventLoop?
) : AbstractCoroutine<T>(parentContext, true, true) {
override val isScopedCoroutine: Boolean get() = true
...
}
在coroutineScope()中:
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R {
...
return suspendCoroutineUninterceptedOrReturn { uCont ->
val coroutine = ScopeCoroutine(uCont.context, uCont)
coroutine.startUndispatchedOrReturn(coroutine, block)
}
}
internal open class ScopeCoroutine<in T>(
context: CoroutineContext,
@JvmField val uCont: Continuation<T> // unintercepted continuation
) : AbstractCoroutine<T>(context, true, true), CoroutineStackFrame {
...
final override val isScopedCoroutine: Boolean get() = true
...
}
BlockingCoroutine、ScopeCoroutine都是AbstractCoroutine的子类,而AbstractCoroutine又继承了JobSupport,两者都是不同类型的Job。所以使用这两种方式创建的Job。如果发生异常cancelParent()方法直接返回了true。这里你可能会感到疑惑,那么他们是如何处理异常的呢?其实在是isScopedCoroutine属性的备注中已经明确的告诉我们了。
是作用域的协程–不传播,将重新抛出。意思就是说我们将在后面的逻辑中处理该异常。
- runBlocking()方法则是在BlockingCoroutine这个Job中的joinBlocking()方法来处理异常:
fun joinBlocking(): T {
...
val state = this.state.unboxState()
// 处理异常
(state as? CompletedExceptionally)?.let { throw it.cause }
return state as T
}
- 而coroutineScope()方法是在ScopeCoroutine类中重写了afterResume()、afterCompletion()方法,他们会把结果值或异常交由外部的协程作用域处理:
override fun afterCompletion(state: Any?) {
// 其他上下文恢复时,默认情况下以可取消的方式恢复
uCont.intercepted().resumeCancellableWith(recoverResult(state, uCont))
}
override fun afterResume(state: Any?) {
// 直接恢复,因为作用域已在正确的上下文中
uCont.resumeWith(recoverResult(state, uCont))
}
7.handleJobException()方法
该方法是定义在JobSupport类中的一个可覆盖的方法:
// 处理父协同程序未处理的最终作业异常。如果它处理异常,则返回true(因此不需要在稍后阶段进行处理)。
// 它被设计为被没有表示异常的结果类型的类启动协同程序(StandaloneCoroutine和ActorCoroutine)覆盖。
// 当作业的最终异常被确定时,在它完成之前,该方法只调用一次。在调用时,作业及其所有子项都已完成。
protected open fun handleJobException(exception: Throwable): Boolean = false
并在其子类StandaloneCoroutine中进行了实现:
private open class StandaloneCoroutine(
parentContext: CoroutineContext,
active: Boolean
) : AbstractCoroutine<Unit>(parentContext, initParentJob = true, active = active) {
override fun handleJobException(exception: Throwable): Boolean {
handleCoroutineException(context, exception)
return true
}
}
上面我们介绍到当多个子Job发生异常的时候,它的父Job会用不同的方式将异常保存下来,但最终会使用第一个子Job抛出的异常来作为取消父Job的原因。handleCoroutineException()方法中的参数context,则是由parentContext和当前Job组合而成的上下文:
// 该协同程序的上下文,包括作为[Job]的该协同程序。
public final override val context: CoroutineContext = parentContext + this
// 这个范围的上下文与这个协同程序的上下文相同。
public override val coroutineContext: CoroutineContext get() = context
handleCoroutineException()方法:
public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
// 从上下文调用异常处理程序(如果存在)
try {
context[CoroutineExceptionHandler]?.let {
it.handleException(context, exception)
return
}
} catch (t: Throwable) {
handleUncaughtCoroutineException(context, handlerException(exception, t))
return
}
// 如果上下文中不存在处理程序,或者引发了异常,则回退到全局处理程序
handleUncaughtCoroutineException(context, exception)
}
分析下这段代码,首先我们从当前上下文中去取CoroutineExceptionHandler这个类型的上下文对象,如果取到了,我们就处理异常,然后直接return。
CoroutineExceptionHandler是一个接口,它的具体实现如下:
public interface CoroutineExceptionHandler : CoroutineContext.Element {
/**
* 协同例程上下文中[CoroutineExceptionHandler]实例的键。
*/
public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>
/**
* 在给定的[context]中处理未捕获的[exception]。它被调用
* 如果协同程序有一个未捕获的异常。
*/
public fun handleException(context: CoroutineContext, exception: Throwable)
}
如果上下文中不存在处理程序,或者引发了异常,则回退到全局处理程序。也就是说如果我们的上下文中没有定义CoroutineExceptionHandler的实现类,最终的异常还是交由系统来处理,这也是为什么我们在BaseContinuationImpl类中捕获了异常后,在没有添加CoroutineExceptionHandler上下文的情况下,应用仍会闪退的原因。
8.Job的收尾工作
if (finalException != null) {
val handled = cancelParent(finalException) || handleJobException(finalException)
if (handled) (finalState as CompletedExceptionally).makeHandled()
}
// 如果覆盖了onCancelling方法则通知子类,否则 do nothing
if (!wasCancelling) onCancelling(finalException)
// 如果覆盖了onCompletionInternal方法则通知子类,否则 do nothing
onCompletionInternal(finalState)
val casSuccess = _state.compareAndSet(state, finalState.boxIncomplete())
assert { casSuccess }
completeStateFinalization(state, finalState)
return finalState
- finalException != null,我们才去处理异常,然后流转job的状态。
- finalException == null正常流转Job的状态
- _state.compareAndSet(state, finalState.boxIncomplete())更改Job的状态
- completeStateFinalization(state, finalState)将子Job从父Job中移除
- 返回finalState
9.completeStateFinalization()方法
// 抑制==在构建最终完成原因时抑制任何异常时为true
private fun completeStateFinalization(state: Incomplete, update: Any?) {
// 现在工作处于最终状态。我们需要正确处理由此产生的状态。
// 这里的各种调用顺序很重要。
// 1) 从父作业中注销。
parentHandle?.let {
it.dispose() // volatile read parentHandle_after_状态已更新
parentHandle = NonDisposableHandle // 释放它以防万一,以帮助GC
}
val cause = (update as? CompletedExceptionally)?.cause
// 2) 调用完成处理程序:.join()、回调等。
// 只在异常处理和其他一切之后调用它们是很重要的,请参见#208
if (state is JobNode) { // SINGLE/SINGLE+状态——一个完成处理程序(common case)
try {
state.invoke(cause)
} catch (ex: Throwable) {
handleOnCompletionException(CompletionHandlerException("Exception in completion handler $state for $this", ex))
}
} else {
state.list?.notifyCompletion(cause)
}
}
10.Job取消和异常的状态流转过程:
总结:
协程取消和异常的处理流程确实很复杂,只有很好的理解了它的工作原理,我们才能更好的在实际开发中去运用它。下面我们就通过对上面的分析来做出一些结论,如果什么不正确的地方,也请读者帮忙提出,本着学习的态度去改正它。
1.取消
- Job的取消是协作的,并不能取消协程的执行流程,只是对Job的状态进行流转,我们需要在必要的地方使用isActive来协作Job的取消
- 如果一个子Job调用cancel()方法来取消协程,那么它不会取消它的父Job(或者说父协程)
- 如果一个父Job调用cancel()方法,它会轮询取消它所有的子Job,并等待所有的子Job成功取消后,继续自行取消
- 调用Job.cancel()时,会创建一个JobCancellationException的异常,该异常会在Job的执行流程中被自行消化,并不会当做异常来处理,也就是不会走到方法handleJobException()中
2.异常
- 如果一个子Job抛异常了,那么默认情况下它会取消它的父Job,并且会通过childCancelled()
方法逐级向上传递,直到根Job - 如果一个父Job抛异常了,它会轮询取消它所有的子Job,并等待所有的子Job成功取消后,继续自行取消
- 如果我们创建协程上下文对象的时候携带了CoroutineExceptionHandler,那么我们的异常就会被捕获到自行处理,否则就交由系统处理,那么应用任会因异常而退出
- 如果我们使用顶层函数SupervisorJob()、supervisorScope()来创建一个Job,如果它的子Job发生异常则不会影响到的它的父Job,原因是重写了childCancelled()的方法
override fun childCancelled(cause: Throwable): Boolean = false
- 如果一个父Job的多个子Job都发生了异常,那么我们总是取第一个异常来作为父Job异常的原因
- 如果父Job发生异常了,那么它将不会在工作了,此时如果我们再调用launch()方法启动一个协程它也不会再工作了。这个结论是上面源码中没有分析,这是笔者在实际开发中遇到的问题,这里也分享给大家:
class MainActivity : AppCompatActivity() {
private val tag = "MainActivity"
private val scope =
CoroutineScope(CoroutineExceptionHandler { _, e -> Log.e(tag, "${e.message}") })
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.button).setOnClickListener {
scope.launch {
throw Exception("throw exception")
}
}
}
}
第一次点击会打印:throw exception,第二次点击将不会有任何的反应,原因是我们在切换线程时会对父Job的状态进行判断如果它已经完成(或者处于异常状态)该协程将会直接使用异常作为结果值,直接恢复,并不会再执行协程体中的代码:
internal abstract class DispatchedTask<in T> internal constructor(
public var resumeMode: Int
) : SchedulerTask() {
...
final override fun run() {
val job = if (exception == null && resumeMode.isCancellableMode) context[Job] else null
if (job != null && !job.isActive) {
// 直接携带异常恢复协程
val cause = job.getCancellationException()
cancelCompletedResult(state, cause)
continuation.resumeWithStackTrace(cause)
} else {
if (exception != null) {
continuation.resumeWithException(exception)
} else {
continuation.resume(getSuccessfulResult(state))
}
}
}
...
}
到这里这篇关于协程取消和异常的文章总算是写完了,如果有什么写的不对的地方欢迎指出,大家一起学习交流。如果您觉得有帮助,麻烦点个赞再走,您的支持就是我创作的最大动力。