kotlin.coroutines
是一套标准库之外的以生产为目的的框架,提供了丰富的 API
让我们可以进行异步操作。虽然这些功能你都可以通过 Kotlin Coroutine
标准库自己来实现。
Kotlin Coroutine suspend 原理解析
Kotlin Coroutine 标准库源码解析
一. CoroutineScope
接口
为何先从 CoroutineScope
接口讲起呢?
因为开启新协程的方法 launch
和 async
都是 CoroutineScope
扩展方法,必须通过 CoroutineScope
实例才能调用。
在
Kotlin Coroutine suspend
原理解析那一章中,我们知道可以通过createCoroutine
创建协程,并调用协程的resumeWith
来启动它;或者通过startCoroutine
方法创建并启动协程。
在kotlin.coroutines
框架下面,我们不需要调用这么底层的方法来启动协程,而是提供了launch
和async
来启动新协程,其实它们最后也是会调用底层的方法来启动协程。
1.1 协程作用域
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
可以看出 CoroutineScope
接口非常简单,只有一个成员属性,协程上下文 CoroutineContext
。
这个
CoroutineContext
属性很重要,CoroutineScope
被称为协程作用域,就是通过CoroutineContext
来实现的。
作用域主要是用来复合协程之间的关系的。针对下面情况:
GlobalScope.launch {
launch {
}
launch {
launch {
}
}
}
这里创建了四个协程,它们之间关系是什么样子的。
当在协程作用域里面,通过 launch
方法创建新协程之后,新协程作用域被称为子协程,当前协程被称为新协程的父协程。它们满足下面规则:
- 当父协程被取消,则所有子协程也跟着都会被取消。
- 父协程需要等待子协程执行完毕之后才会最终进入完成状态,不管父协程自身的协程体是否已经执行完成。
- 子协程会继承父协程的协程上下文集合中的元素,如果子协程有相同
KEY
的元素,那么会覆盖父协程对应元素。
1.2 newCoroutineContext 方法
为新协程创建上下文 CoroutineContext
对象,它是 CoroutineScope
的扩展方法。
@ExperimentalCoroutinesApi
public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
val combined = coroutineContext + context
val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
debug + Dispatchers.Default else debug
}
主要流程:
- 将当前协程作用域中的协程上下文对象
coroutineContext
与方法参数中的上下文对象context
合并相加。 - 如果是
debug
模式,在上下文对象中添加CoroutineId
元素 - 如果合并之后的上下文对象
combined
中没有协程拦截器ContinuationInterceptor
元素,那么就将Dispatchers.Default
元素添加到上下文集合中。
1.3 launch 方法
创建新协程,返回Job
对象实例;它也是 CoroutineScope
的扩展方法
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
}
它有三个参数:
-
context
: 额外添加的协程上下文元素集合,默认是空元素集合EmptyCoroutineContext
-
start
: 新协程的启动模式;有四种模式DEFAULT
、LAZY
、ATOMIC
、UNDISPATCHED
-
block
: 协程体,新协程的执行代码
方法主要流程:
- 通过
newCoroutineContext
方法为新协程创建新的协程上下文对象newContext
- 根据启动模式的不同,创建
StandaloneCoroutine
或者LazyStandaloneCoroutine
协程对象coroutine
。
LazyStandaloneCoroutine
是StandaloneCoroutine
的子类
- 调用
coroutine
的start
方法,并返回这个对象,它也是Job
的子类。
通过返回的这个
Job
对象,可以调用它的join
方法等待协程完成,或者调用cancel
方法取消协程。
1.4 async 方法
也是创建新协程,但是它返回 Deferred
对象,它是 Job
的子类,但是它可以返回结果值。相当于 java
中的 Runnable
和 Callable
的区别。
async
方法与launch
方法相比,它可以得到协程执行之后的结果值。
public fun CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyDeferredCoroutine(newContext, block) else
DeferredCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
方法参数和 launch
方法几乎一样,只不过它的协程体 block
方法有返回值。
方法流程也和 launch
方法几乎一样,只不过创建了 DeferredCoroutine
或者 LazyDeferredCoroutine
对象的协程。
二. AbstractCoroutine 类
在上一节中,launch
和 async
方法返回的 StandaloneCoroutine
和 DeferredCoroutine
, 都有一个共同父类 AbstractCoroutine
。
2.1 成员属性
public abstract class AbstractCoroutine(
/**
* The context of the parent coroutine.
*/
@JvmField
protected val parentContext: CoroutineContext,
active: Boolean = true
) : JobSupport(active), Job, Continuation, CoroutineScope {
@Suppress("LeakingThis")
public final override val context: CoroutineContext = parentContext + this
/**
* The context of this scope which is the same as the [context] of this coroutine.
*/
public override val coroutineContext: CoroutineContext get() = context
override val isActive: Boolean get() = super.isActive
....
}
从 AbstractCoroutine
的继承关系中可以看出:
- 它是
Job
的子类,说明它是一个任务,可以调用Job
的cancel
方法取消它,或者调用join
方法等待这个任务完成。 - 它是
Continuation
子类,说明它是一个挂起点,可以调用resumeWith
方法恢复。 - 它是
CoroutineScope
子类,说明它也是一个协程作用域。
重要的成员属性:
-
parentContext
: 表示父协程的上下文。
这个属性非常重要,它里面有父协程的任务
Job
,用来实现父协程任务取消时,子协程联动取消;或者子协程发生异常的时候,取消父协程的任务。
-
active
: 表示当前AbstractCoroutine
代表的任务,创建时是处于活跃状态,还是不活跃的新建状态。
注:它不是
AbstractCoroutine
的成员属性,只是创建时传递的参数,因为它没有使用val
或者var
修饰。
-
context
: 当前AbstractCoroutine
协程的协程上下文。parentContext + this
; 因为AbstractCoroutine
是Job
的子类,所以它会替换父协程上下文parentContext
集合中的Job
元素。
context
和parentContext
区别就是,两个上下文集合中Job
元素不同。parentContext
是父协程任务Job
,context
是本协程(子协程) 的任务Job
。
-
coroutineContext
:协程作用域的协程上下文,这里与context
相同。 -
isActive
:表示当前任务Job
是否处于活跃状态。
2.2 重要方法
2.2.1 start 方法
public fun start(start: CoroutineStart, block: suspend () -> T) {
initParentJob()
start(block, this)
}
public fun start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
initParentJob()
start(block, receiver, this)
}
在 launch
和 async
方法流程中,在创建完 AbstractCoroutine
子类对象后,都会调用它的 start
方法。
方法流程:先调用 initParentJob
方法,将父协程任务 Job
与本协程联系起来;再调用 CoroutineStart
的 invoke
方法运行协程体 block
。
2.2.2 initParentJob 方法
internal fun initParentJob() {
initParentJobInternal(parentContext[Job])
}
// 在 JobSupport 类中
internal fun initParentJobInternal(parent: Job?) {
assert { parentHandle == null }
if (parent == null) {
parentHandle = NonDisposableHandle
return
}
parent.start() // make sure the parent is started
@Suppress("DEPRECATION")
val handle = parent.attachChild(this)
parentHandle = handle
// now check our state _after_ registering (see tryFinalizeSimpleState order of actions)
if (isCompleted) {
handle.dispose()
parentHandle = NonDisposableHandle // release it just in case, to aid GC
}
}
可以看出 initParentJob
方法就是将父协程的 Job
与子协程联系起来。
2.3 重要子类
2.3.1 StandaloneCoroutine 类
private open class StandaloneCoroutine(
parentContext: CoroutineContext,
active: Boolean
) : AbstractCoroutine(parentContext, active) {
override fun handleJobException(exception: Throwable): Boolean {
handleCoroutineException(context, exception)
return true
}
}
launch
方法创建的默认 Job
, 可以看出 StandaloneCoroutine
只是复写了处理任务异常 handleJobException
方法。
2.3.2 LazyStandaloneCoroutine 类
private class LazyStandaloneCoroutine(
parentContext: CoroutineContext,
block: suspend CoroutineScope.() -> Unit
) : StandaloneCoroutine(parentContext, active = false) {
private val continuation = block.createCoroutineUnintercepted(this, this)
override fun onStart() {
continuation.startCoroutineCancellable(this)
}
}
它是 StandaloneCoroutine
的子类,active = false
表示这个任务 Job
创建时不处于活跃状态。
- 通过
block.createCoroutineUnintercepted(this, this)
创建suspend
的block
方法对应的协程。
suspend
函数想要在非suspend
方法中运行,只能通过suspend
函数扩展方法createCoroutineUnintercepted
创建协程,再调用它的resumeWith
方法运行协程;我们在 Kotlin Coroutine suspend 原理解析有详细介绍。
- 在
onStart
方法中,调用continuation.startCoroutineCancellable(this)
方法,启动协程,即执行block
函数。
onStart
在AbstractCoroutine
的onStartInternal
方法中调用,而onStartInternal
方法只有当任务Job
从不活跃状态转成活跃状态时候,才会调用一次。
也就是说
LazyStandaloneCoroutine
中的block
函数,再刚创建的时候,并不会直接执行;只有当LazyStandaloneCoroutine
对应任务从不活跃状态变成活跃状态才会执行。
任务Job
从不活跃状态变成活跃状态,通过调用它的job
的start
或者join
方法。顺便说一句Deferred
类的await
方法也是可以的。
2.3.3 DeferredCoroutine 类
public interface Deferred : Job {
public suspend fun await(): T
public val onAwait: SelectClause1
@ExperimentalCoroutinesApi
public fun getCompleted(): T
@ExperimentalCoroutinesApi
public fun getCompletionExceptionOrNull(): Throwable?
}
private open class DeferredCoroutine(
parentContext: CoroutineContext,
active: Boolean
) : AbstractCoroutine(parentContext, active), Deferred, SelectClause1 {
override fun getCompleted(): T = getCompletedInternal() as T
override suspend fun await(): T = awaitInternal() as T
override val onAwait: SelectClause1 get() = this
override fun registerSelectClause1(select: SelectInstance, block: suspend (T) -> R) =
registerSelectClause1Internal(select, block)
}
可以看出 Deferred
是 Job
的子类,它可以获取任务执行后的结果值。
DeferredCoroutine
也是继承 AbstractCoroutine
类,但是可以通过 await
或者 getCompleted
获取结果值。
2.3.4 LazyDeferredCoroutine 类
private class LazyDeferredCoroutine(
parentContext: CoroutineContext,
block: suspend CoroutineScope.() -> T
) : DeferredCoroutine(parentContext, active = false) {
private val continuation = block.createCoroutineUnintercepted(this, this)
override fun onStart() {
continuation.startCoroutineCancellable(this)
}
}
这个几乎 LazyStandaloneCoroutine
实现流程一样。
三. CoroutineStart 类
在 AbstractCoroutine
类中,我们知道它的 start
方法最终会调用到 CoroutineStart
的 invoke
方法。
CoroutineStart
是一个枚举类型,有四个值,分别是 DEFAULT
、LAZY
、ATOMIC
和 UNDISPATCHED
;它们之间的不同,通过分析 invoke
方法就明白了。
@InternalCoroutinesApi
public operator fun invoke(block: suspend () -> T, completion: Continuation): Unit =
when (this) {
DEFAULT -> block.startCoroutineCancellable(completion)
ATOMIC -> block.startCoroutine(completion)
UNDISPATCHED -> block.startCoroutineUndispatched(completion)
LAZY -> Unit // will start lazily
}
@InternalCoroutinesApi
public operator fun invoke(block: suspend R.() -> T, receiver: R, completion: Continuation): Unit =
when (this) {
DEFAULT -> block.startCoroutineCancellable(receiver, completion)
ATOMIC -> block.startCoroutine(receiver, completion)
UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
LAZY -> Unit // will start lazily
}
这两个方法流程一样,只不过下面那个方法多接受 receiver
参数。
3.1 DEFAULT
调用 startCoroutineCancellable
方法,在指定的拦截器线程中执行协程,也就是 block
函数;但是在运行 block
函数之前,用户是可以手动取消,不让执行的。
internal fun (suspend (R) -> T).startCoroutineCancellable(receiver: R, completion: Continuation) =
runSafely(completion) {
createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit))
}
从 startCoroutineCancellable
方法实现来看:
runSafely
方法保证协程抛出未捕捉异常时,会回调completion.resumeWith(Result.failure(e))
通知失败。通过
createCoroutineUnintercepted
方法,会创建suspend (R) -> T
函数对应的协程ContinuationImpl
对象。并将completion
的上下文对象context
赋值给ContinuationImpl
对象的context
。 这个具体请看Kotlin Coroutine suspend 原理解析调用
intercepted()
方法,其实就是调用ContinuationImpl
里的intercepted
方法
public fun intercepted(): Continuation =
intercepted
?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also { intercepted = it }
从协程上下文 context
获取拦截器 ContinuationInterceptor
对象,并调用它的 interceptContinuation
方法,获取被拦截器包装后的协程 Continuation
对象。
- 最后调用
resumeCancellableWith
方法,启动可取消的协程。
注意这里的协程,已经是包装后的协程
Continuation
对象,也就是说会经过拦截器的方法。一般拦截器都是实现线程的切换。
3.2 ATOMIC
调用 startCoroutine
方法,在指定的拦截器线程中直接执行协程,也就是 block
函数。
@SinceKotlin("1.3")
@Suppress("UNCHECKED_CAST")
public fun (suspend R.() -> T).startCoroutine(
receiver: R,
completion: Continuation
) {
createCoroutineUnintercepted(receiver, completion).intercepted().resume(Unit)
}
可以看出与 startCoroutineCancellable
方法大体相同。
通过 createCoroutineUnintercepted
方法创建 suspend R.() -> T
函数对应的协程体 ContinuationImpl
对象,再调用 intercepted
方法得到被拦截器封装的 Continuation
对象,最后调用 resume
直接执行协程,也就是 suspend R.() -> T
函数。
唯一的不同就是
resumeCancellableWith
方法,再还未执行协程,也就是运行suspend R.() -> T
函数之前,是可以取消的。
而resume
方法会直接执行协程,也就是运行suspend R.() -> T
函数,直到遇到第一个挂起点。
3.3 UNDISPATCHED
调用 startCoroutineUndispatched
方法,直接在当前线程执行协程,也就是 block
函数。
internal fun (suspend (R) -> T).startCoroutineUndispatched(receiver: R, completion: Continuation) {
startDirect(completion) { actualCompletion ->
withCoroutineContext(completion.context, null) {
startCoroutineUninterceptedOrReturn(receiver, actualCompletion)
}
}
}
这个方法与 startCoroutineCancellable
和 startCoroutine
差距很大。
其中最大的差距就是
startCoroutineCancellable
和startCoroutine
都会调用协程的intercepted()
包装一下,这样执行协程的时候,会经过协程拦截器,这样实现线程之间的切换。
但是这个方法没有调用,而是直接执行startCoroutineUninterceptedOrReturn
方法
// 在 IntrinsicsJvm 类中,有具体实现
@SinceKotlin("1.3")
@InlineOnly
public actual inline fun (suspend R.() -> T).startCoroutineUninterceptedOrReturn(
receiver: R,
completion: Continuation
): Any? = (this as Function2, Any?>).invoke(receiver, completion)
可以看出,通过invoke
方式,直接调用协程体(即 (suspend R.() -> T)
函数);所以调用的时候,是不经过协程拦截器的,也就是说直接在当前线程执行协程体。
注: 协程体根据内部挂起点分段执行的(挂起点就是调用
suspend
函数的地方), 而从挂起点返回后,协程体函数在那个线程执行,那这个就需要看具体实现了。
3.4 LAZY
LAZY -> Unit // will start lazily
可以看出不做任何操作啊,而协程体 (即 suspend R.() -> T
block函数)调用是通过 LazyStandaloneCoroutine
和 LazyDeferredCoroutine
中实现的。
四. CoroutineDispatcher
协程分配器,协程之间的线程切换就是通过这个 CoroutineDispatcher
来实现的。
public abstract class CoroutineDispatcher :
AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
.......
}
它是协程拦截器 ContinuationInterceptor
的子类,通过 AbstractCoroutineContextElement(ContinuationInterceptor)
知道,默认情况下它的 Element
的 key
就是 ContinuationInterceptor
,在协程上下文 context
元素集合中只会存在一个 ContinuationInterceptor
类型元素。
4.1 isDispatchNeeded 方法
协程是否需要进行调度。默认情况下都是返回 true
public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true
如果返回 true
,就会调用 dispatch
方法;如果返回 false
,那么协程就在当前线程恢复。
4.2 dispatch 方法
public abstract fun dispatch(context: CoroutineContext, block: Runnable)
是一个 abstract
方法,由子类来实现,进行线程之间切换的。
一般情况下,使用一个线程池,将
block
放在线程池中执行,这样就实现线程之间切换了。
4.3 interceptContinuation 方法
public final override fun interceptContinuation(continuation: Continuation): Continuation =
DispatchedContinuation(this, continuation)
复写了协程拦截器 ContinuationInterceptor
中的方法。返回包装后的 DispatchedContinuation
协程对象。
override fun resumeWith(result: Result) {
val context = continuation.context
val state = result.toState()
if (dispatcher.isDispatchNeeded(context)) {
_state = state
resumeMode = MODE_ATOMIC_DEFAULT
dispatcher.dispatch(context, this)
} else {
executeUnconfined(state, MODE_ATOMIC_DEFAULT) {
withCoroutineContext(this.context, countOrElement) {
continuation.resumeWith(result)
}
}
}
}
inline fun resumeCancellableWith(result: Result) {
val state = result.toState()
if (dispatcher.isDispatchNeeded(context)) {
_state = state
resumeMode = MODE_CANCELLABLE
dispatcher.dispatch(context, this)
} else {
executeUnconfined(state, MODE_CANCELLABLE) {
if (!resumeCancelled()) {
resumeUndispatchedWith(result)
}
}
}
}
它们是 DispatchedContinuation
类 resume
方法实现。
通过
CoroutineDispatcher
的isDispatchNeeded
方法判断是否需要调度。如果返回true
,则调用dispatch
方法进行线程切换;负责通过continuation.resumeWith(result)
方法直接在当前线程恢复协程。
五. withContext 方法
切换协程上下文环境。一般情况下都是 suspend
函数执行的线程。
public suspend fun withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
// compute new context
val oldContext = uCont.context
val newContext = oldContext + context
// always check for cancellation of new context
newContext.checkCompletion()
// FAST PATH #1 -- new context is the same as the old one
if (newContext === oldContext) {
val coroutine = ScopeCoroutine(newContext, uCont)
return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
}
// FAST PATH #2 -- the new dispatcher is the same as the old one (something else changed)
// `equals` is used by design (see equals implementation is wrapper context like ExecutorCoroutineDispatcher)
if (newContext[ContinuationInterceptor] == oldContext[ContinuationInterceptor]) {
val coroutine = UndispatchedCoroutine(newContext, uCont)
// There are changes in the context, so this thread needs to be updated
withCoroutineContext(newContext, null) {
return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
}
}
// SLOW PATH -- use new dispatcher
val coroutine = DispatchedCoroutine(newContext, uCont)
coroutine.initParentJob()
block.startCoroutineCancellable(coroutine, coroutine)
coroutine.getResult()
}
}
方法流程分为三个部分, 根据新旧协程上下文不同来区分:
- 新旧上下文一样, 即
newContext === oldContext
,说明没有任何改变,那么就在当前上下文环境,当前线程执行block
的suspend
方法。 - 新旧上下文不一样,但是拥有的协程拦截器
ContinuationInterceptor
一样,也就是说执行线程是一样的,那么就在当前线程切换一下协程上下文newContext
执行block
的suspend
方法。 - 新旧上下文不一样, 拥有的协程拦截器
ContinuationInterceptor
也不一样;
那么既要切换上下文环境也需要切换线程 执行block
的suspend
方法。
这三种情况,返回的协程包装类也是不一样的,分别是
ScopeCoroutine
、UndispatchedCoroutine
、DispatchedCoroutine
。
其中UndispatchedCoroutine
和DispatchedCoroutine
都是ScopeCoroutine
子类。
当block
的suspend
方法执行完成之后,都会调用ScopeCoroutine
、UndispatchedCoroutine
、DispatchedCoroutine
的afterResume
方法。
针对 ScopeCoroutine
类
override fun afterResume(state: Any?) {
// Resume direct because scope is already in the correct context
uCont.resumeWith(recoverResult(state, uCont))
}
直接调用 resumeWith
方法恢复协程,不需要切换线程,也不需要切换上下文。
针对 UndispatchedCoroutine
类
override fun afterResume(state: Any?) {
// resume undispatched -- update context by stay on the same dispatcher
val result = recoverResult(state, uCont)
withCoroutineContext(uCont.context, null) {
uCont.resumeWith(result)
}
}
可以看出要将协程上下文切换到旧的 uCont.context
, 然后在调用 resumeWith
方法恢复协程。
针对 DispatchedCoroutine
类
override fun afterResume(state: Any?) {
if (tryResume()) return // completed before getResult invocation -- bail out
// Resume in a cancellable way because we have to switch back to the original dispatcher
uCont.intercepted().resumeCancellableWith(recoverResult(state, uCont))
}
要通过 uCont.intercepted()
方法,在 uCont
协程拦截器中恢复,也就是说将线程切换到协程调度线程中。