在调用launch方法启动协程时,内部会调用AbstractCoroutine类的start方法,代码如下:
public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
initParentJob()
start(block, receiver, this)
}
之前在Kotlin协程:创建、启动、挂起、恢复中提到,initParentJob方法用于绑定父协程,代码如下:
internal fun initParentJob() {
initParentJobInternal(parentContext[Job])
}
该方法从父协程的上下文中获取父协程的Job对象,调用initParentJobInternal方法,代码如下:
// 用于保存父协程的handler
private val _parentHandle = atomic<ChildHandle?>(null)
internal var parentHandle: ChildHandle?
get() = _parentHandle.value
set(value) { _parentHandle.value = value }
internal fun initParentJobInternal(parent: Job?) {
assert { parentHandle == null }
// 如果没有父协程
if (parent == null) {
// 赋默认值NonDisposableHandle
parentHandle = NonDisposableHandle
// 返回
return
}
// 如果有父协程,保证父协程启动
parent.start()
// 父子协程绑定,返回父协程handler
@Suppress("DEPRECATION")
val handle = parent.attachChild(this)
// 保存到全局变量
parentHandle = handle
// 如果当前协程执行完成
if (isCompleted) {
// 释放父协程
handle.dispose()
// 赋默认值NonDisposableHandle
parentHandle = NonDisposableHandle
}
}
从上面的代码可以知道,父子协程通过attachChild方法实现绑定,代码如下:
@Suppress("OverridingDeprecatedMember")
public final override fun attachChild(child: ChildJob): ChildHandle {
return invokeOnCompletion(onCancelling = true,
handler = ChildHandleNode(this, child).asHandler) as ChildHandle
}
这里与之前在Kotlin协程:生命周期原理中分析的协程生命周期方法类似,也是将父子协程封装成ChildHandleNode对象,之后进行类型转换。最终通过invokeOnCompletion方法获取ChildHandle对象。这里的逻辑可以理解成子协程监听父协程完成。方法中的onCancelling参数表示是否同时监听取消事件,代码如下:
// onCancelling = true,表示是否监听取消事件
// invokeImmediately = true,表示过程发生异常,是否立刻回调
public final override fun invokeOnCompletion(
onCancelling: Boolean,
invokeImmediately: Boolean,
handler: CompletionHandler
): DisposableHandle {
// 局部缓存
var nodeCache: JobNode<*>? = null
// 循环
loopOnState { state ->
// 根据状态进行处理
when (state) {
// 没有监听器
is Empty -> {
// 如果已经启动——EMPTY_A状态
if (state.isActive) {
// 将当前的handler封装成JobNode对象
val node = nodeCache ?: makeNode(handler, onCancelling).also { nodeCache = it }
// 通过CAS保存,保存完变成SINGLE状态,返回
if (_state.compareAndSet(state, node)) return node
} else // 如果未启动——EMPTY_N状态
// 直接变成LIST_N状态
promoteEmptyToNodeList(state)
}
// 如果没有执行结束,同时还不是EMPTY类型的状态
is Incomplete -> {
// 获取list
val list = state.list
// 如果list为空——SINGLE状态
if (list == null) {
// 则转成LIST_A状态,中间会经过SINGLE+状态
promoteSingleToNodeList(state as JobNode<*>)
} else { // 如果是SINGLE+或LIST_A状态
var rootCause: Throwable? = null
var handle: DisposableHandle = NonDisposableHandle
// 如果监听取消事件,同时当前协程已经执行完
if (onCancelling && state is Finishing) {
synchronized(state) {
// 获取异常根本原因
rootCause = state.rootCause
// 如果rootCause为空,说明还没有进入到Cancelling状态
// 或者当前的handler是ChildHandleNode类型的,同时不是Completing状态
if (rootCause == null || handler.isHandlerOf<ChildHandleNode>() && !state.isCompleting) {
// 对handler进行封装
val node = nodeCache ?: makeNode(handler, onCancelling).also { nodeCache = it }
// 把node添加到list后面,并保存state,返回,再次循环重试,确保确实没有发生取消
if (!addLastAtomic(state, list, node)) return@loopOnState // retry
// 如果rootCause为空,确实没有取消,则返回
if (rootCause == null) return node
// 如果发生了取消,则保存node
handle = node
}
}
}
// 如果已经发生了取消
if (rootCause != null) {
// 如果允许立即通知,则直接回调通知
if (invokeImmediately) handler.invokeIt(rootCause)
// 返回
return handle
} else { // 如果没有发生取消
// 对handler进行封装
val node = nodeCache ?: makeNode(handler, onCancelling).also { nodeCache = it }
// 把node添加到list后面,并保存state,返回
if (addLastAtomic(state, list, node)) return node
}
}
}
else -> {// 其他情况,协程已经进入完成状态
// 如果允许立即通知,则直接回调通知
if (invokeImmediately) handler.invokeIt((state as? CompletedExceptionally)?.cause)
// 返回默认值,绑定失败
return NonDisposableHandle
}
}
}
}
在协程中,可以通过协程上下文获取Job对象,但在我们实际编写代码的过程中,并没有向协程上下文中添加Job对象。那么,协程如何在上下文中添加Job对象呢?首先,看下面的例子:
val job1 = launch {
val job2 = coroutineContext[Job]
val job3 = launch {
val job4 = coroutineContext[Job]
}
}
问题:上面代码中的四个Job对象是什么关系?
答案:job1和job2是同一个对象,job3和job4是同一个对象。
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
}
在调用launch方法启动协程时,会调用newCoroutineContext方法重新计算协程上下文,由于参数block是CoroutineScope类的扩展方法,因此可以获取到CoroutineScope类中的常量coroutineContext,它就是父协程的上下文,用这个上下文再加上我们在参数中指定的上下文就成了我们的新的上下文。这就是newCoroutineContext方法的执行逻辑。
由于此时我们并没有添加新的Job对象覆盖父协程的Job对象,因此通过上下文获取的Job对象就是父协程的Job对象。
那么获取到的父协程的Job对象是在什么时候被添加到上下文中的呢?
Job对象添加的实现在LazyStandaloneCoroutine类和StandaloneCoroutine类的父类AbstractCoroutine类中,代码入下:
@InternalCoroutinesApi
public abstract class AbstractCoroutine<in T>(
// 参数为父协程的上下文
@JvmField
protected val parentContext: CoroutineContext,
active: Boolean = true
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
// 重写Continuation接口的常量,将自身加入到了上下文中,自身实现了Job接口
@Suppress("LeakingThis")
public final override val context: CoroutineContext = parentContext + this
// 重写CoroutineScope接口的常量,返回添加完自身的上下文
public override val coroutineContext: CoroutineContext get() = context
...
}
AbstractCoroutine通过重写Continuation接口与CoroutineScope接口的常量,将自身作为Job对象加入到上下文中,覆盖了父协程Job,使子协程在绑定时可以通过参数中的parentContext获取到对应的父协程的Job对象。
launch方法最后返回的Job对象也是同一个AbstractCoroutine对象,因此得到的Job对象是相同的。