回顾携程
先梳理一下协程的执行流程,这一点真是每次不看每次忘,需要时时拿出来温习。
1.launch开启一个协程,构建coroutine,调用coroutine.start()
2.构建coroutineStart,调用invoke函数
3.调用 block.startCoroutine(receiver, completion)
4.coroutine最核心代码:
createCoroutineUnintercepted(receiver, completion).intercepted().resume(Unit)
上面这行代码实际是构建了continuation,然后调用 intercepted(),最后调用resume方法。
5.找到continuation的实现类:BaseContinuationImpl,查看resume()方法。
6.开启了一个while循环,然后调用invokeSuspend() 方法, 这个invokeSuspend()即是我们写的kotlin代码生成的方法。
** 协程本质是利用语法糖帮我们实现异步回调,用结构化并发的思想去写代码。需要注意的是每次遇到挂起函数,后面的方法调用都可以理解为封装到挂起函数的回调里面。 **
猜测
根据上面的流程,我们知道协程的核心其实是 continuation.resume()方法。 如果协程想要取消,猜测是continuation.resume(error)这种情况,取消挂起函数。
协程的取消流程:
1.入口代码
public fun CoroutineContext.cancel(cause: CancellationException? = null) {
this[Job]?.cancel(cause)
}
2.JobSupport.cancel()
public override fun cancel(cause: CancellationException?) {
cancelInternal(cause ?: defaultCancellationException())
}
3.JobSupport.cancelImpl()方法,发现kotlin源码特别喜欢用impl关键字,算是一种命名规范吧
internal fun cancelImpl(cause: Any?): Boolean {
var finalState: Any? = COMPLETING_ALREADY
if (onCancelComplete) {
// make sure it is completing, if cancelMakeCompleting returns state it means it had make it
// completing and had recorded exception
finalState = cancelMakeCompleting(cause)
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
}
}
}
4. JobSupport.makeCancelling()方法
private fun makeCancelling(cause: Any?): Any? {
var causeExceptionCache: Throwable? = null // lazily init result of createCauseException(cause)
loopOnState { state ->
when (state) {
is Finishing -> { // already finishing -- collect exceptions
notifyRootCause?.let { notifyCancelling(state.list, it) } -----> 通知关闭
return COMPLETING_ALREADY
}
is Incomplete -> {
}
else -> return TOO_LATE_TO_CANCEL // already complete
}
}
}
5. JobSupport.notifyCancelling()方法,最核心代码,第三行是决定是否取消父协程,关于supervisorJob有一些知识点
private fun notifyCancelling(list: NodeList, cause: Throwable) {
// first cancel our own children
onCancelling(cause)
notifyHandlers<JobCancellingNode>(list, cause)
// then cancel parent
cancelParent(cause) // tentative cancellation -- does not matter if there is no parent
}
6. JobSupport.notifyHandlers() 通知节点
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) }
}
7. ChildContinuation.invoke()
internal class ChildContinuation(
@JvmField val child: CancellableContinuationImpl<*>
) : JobCancellingNode() {
override fun invoke(cause: Throwable?) {
child.parentCancelled(child.getContinuationCancellationCause(job))
}
}
8. CancellableContinuationImpl.parentCancelled()
internal fun parentCancelled(cause: Throwable) {
if (cancelLater(cause)) return
cancel(cause)
// Even if cancellation has failed, we should detach child to avoid potential leak
detachChildIfNonResuable()
}
9. CancellableContinuationImpl.cancel()
public override fun cancel(cause: Throwable?): Boolean {
_state.loop { state ->
if (state !is NotCompleted) return false // false if already complete or cancelling
// Active -- update to final state
val update = CancelledContinuation(this, cause, handled = state is CancelHandler)
if (!_state.compareAndSet(state, update)) return@loop // retry on cas failure
// Invoke cancel handler if it was present
(state as? CancelHandler)?.let { callCancelHandler(it, cause) }
// Complete state update
detachChildIfNonResuable()
dispatchResume(resumeMode) // no need for additional cancellation checks
return true
}
}
10. DispatchedTask
internal fun <T> DispatchedTask<T>.dispatch(mode: Int) {
assert { mode != MODE_UNINITIALIZED } // invalid mode value for this method
val delegate = this.delegate
val undispatched = mode == MODE_UNDISPATCHED
if (!undispatched && delegate is DispatchedContinuation<*> && mode.isCancellableMode == resumeMode.isCancellableMode) {
// dispatch directly using this instance's Runnable implementation
val dispatcher = delegate.dispatcher
val context = delegate.context
if (dispatcher.isDispatchNeeded(context)) {
dispatcher.dispatch(context, this)
} else {
resumeUnconfined()
}
} else {
// delegate is coming from 3rd-party interceptor implementation (and does not support cancellation)
// or undispatched mode was requested
resume(delegate, undispatched)
}
}
11. DispatchedTask.run()方法
public final override fun run() {
assert { resumeMode != MODE_UNINITIALIZED } // should have been set before dispatching
val taskContext = this.taskContext
var fatalException: Throwable? = null
try {
val delegate = delegate as DispatchedContinuation<T>
val continuation = delegate.continuation
withContinuationContext(continuation, delegate.countOrElement) {
val context = continuation.context
val state = takeState() // NOTE: Must take state in any case, even if cancelled
val exception = getExceptionalResult(state)
/*
* Check whether continuation was originally resumed with an exception.
* If so, it dominates cancellation, otherwise the original exception
* will be silently lost.
*/
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))
}
}
}
} catch (e: Throwable) {
// This instead of runCatching to have nicer stacktrace and debug experience
fatalException = e
} finally {
val result = runCatching { taskContext.afterTask() }
handleFatalException(fatalException, result.exceptionOrNull())
}
}
12. Continuation扩展方法
@InlineOnly
public inline fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =
resumeWith(Result.failure(exception))
13. CancellableContinuationImpl.resumeImpl()
private fun resumeImpl(
proposedUpdate: Any?,
resumeMode: Int,
onCancellation: ((cause: Throwable) -> Unit)? = null
) {
_state.loop { state ->
when (state) {
is NotCompleted -> {
val update = resumedState(state, proposedUpdate, resumeMode, onCancellation, idempotent = null)
if (!_state.compareAndSet(state, update)) return@loop // retry on cas failure
detachChildIfNonResuable()
dispatchResume(resumeMode) // dispatch resume, but it might get cancelled in process
return // done
}
is CancelledContinuation -> {
/*
* If continuation was cancelled, then resume attempt must be ignored,
* because cancellation is asynchronous and may race with resume.
* Racy exceptions will be lost, too.
*/
if (state.makeResumed()) { // check if trying to resume one (otherwise error)
// call onCancellation
onCancellation?.let { callOnCancellation(it, state.cause) }
return // done
}
}
}
alreadyResumedError(proposedUpdate) // otherwise, an error (second resume attempt)
}
}
历时12步,终于知道协程是如何取消的了,不得不说,协程取消实在太麻烦了。
总结
我们可以了解到的知识点:
1.协程取消是针对挂起函数后面的代码进行取消,如delay(),withContext(),如果没有挂起函数,协程的任务会继续进行执行。
2.对于没有挂起函数的情况下,可以使用 isActive,或者 ensureActive() 直接在协程里面跑取消异常,只能抛出这个异常,协程结束。
3.协程内部处理了 CancellationException,可以理解为我们手动调用job.cacel() 其实也是在内部抛出了这个异常。 如果想处理这个异常,可以使用 withContext(NonCancellable),或者 withContext(timeOut)
4.我们构建coroutineContext,一般使用 SupervisorJob,直接忽略子协程的取消。
private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {
override fun childCancelled(cause: Throwable): Boolean = false
}
问题
子协程cancel,是否同步取消父协程?
public open fun childCancelled(cause: Throwable): Boolean {
if (cause is CancellationException) return true
return cancelImpl(cause) && handlesException
}
需要区分 SupervisorJob和普通Job类型
前者子协程取消跟自己无关,
后者需要区分取消是怎么造成的,如果是子协程调用cancel方法,和自己无关; 如果子协程异常了,自己也会取消,不过已经异常的话,直接程序崩溃了都。