在前面讲如何开启一个原始协程的时候,我们知道:
协程执行流程有3步,createCoroutineUnintercepted创建、intercepted拦截、resumeWith执行
看到intercepted,自然而然想到的就是拦截
查看源码:
public fun intercepted(): Continuation<Any?> =
intercepted
?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also { intercepted = it }
public interface ContinuationInterceptor : CoroutineContext.Element {
companion object Key : CoroutineContext.Key<ContinuationInterceptor>
public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
}
ContinuationInterceptor继承于CoroutineContext.Element,所以它也是CoroutineContext,同时提供了interceptContinuation方法,先记住这个方法后续会用到。
现在我们来看Dispatchers.Main,为什么它能切换到主线程呢?
public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
public abstract class MainCoroutineDispatcher : CoroutineDispatcher()
发现MainCoroutineDispatcher继承于CoroutineDispatcher,主角登场了,但还不够,我们继续看CoroutineDispatcher是什么
public abstract class CoroutineDispatcher :
AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true
public abstract fun dispatch(context: CoroutineContext, block: Runnable)
public open fun dispatchYield(context: CoroutineContext, block: Runnable) = dispatch(context, block)
public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
DispatchedContinuation(this, continuation)
}
原来CoroutineDispatcher实现了ContinuationInterceptor,说明CoroutineDispatcher也具有拦截器的功能
再来看CoroutineDispatcher提供的几个方法:
- isDispatchNeeded:判断是否需要分发
- dispatch:如何进行分发
- interceptContinuation:直接返回了
DispatchedContinuation对象,它是一个Continuation类型。那么自然重点就是它的resumeWith方法。
override fun resumeWith(result: Result<T>) {
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)
}
}
}
}
这里我们看到了isDispatchNeeded与dispatch方法,如果不需要分发自然是直接调用原始的continuation对象的resumeWith方法,也就没有什么类似于线程的切换。
那什么时候isDispatcheNeeded为true呢?这就要看它的dispatcer是什么。
由于现在我们是拿Dispatchers.Main作分析。所以这里我直接告诉你们它的dispatcher是HandlerContext
override fun createDispatcher(allFactories: List<MainDispatcherFactory>) =
HandlerContext(Looper.getMainLooper().asHandler(async = true), "Main")
internal class HandlerContext private constructor(
private val handler: Handler,
private val name: String?,
private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {
/**
* Creates [CoroutineDispatcher] for the given Android [handler].
*
* @param handler a handler.
* @param name an optional name for debugging.
*/
public constructor(
handler: Handler,
name: String? = null
) : this(handler, name, false)
@Volatile
private var _immediate: HandlerContext? = if (invokeImmediately) this else null
override val immediate: HandlerContext = _immediate ?:
HandlerContext(handler, name, true).also { _immediate = it }
override fun isDispatchNeeded(context: CoroutineContext): Boolean {
return !invokeImmediately || Looper.myLooper() != handler.looper
}
override fun dispatch(context: CoroutineContext, block: Runnable) {
handler.post(block)
}
}
HandlerContext继承HandlerDispatcher,而HandlerDispatcher继承MainCoroutineDispatcher
条件都符合,我们直接看isDispatchNeeded方法返回true的逻辑。
- 首先通过
invokeImmediately判断,它代表当前线程是否与自身的线程相同,如果外部使用者能够保证这一点,就可以直接使用Dispatcher.Main.immediate来避免进行线程的切换逻辑。 - 当然为了保证外部的判断失败,最后也会通过
Looper.myLooper() != handler.looper来进行校正。对于Dispatchers.Main这个的handle.looper自然是主线程的looper。
如果不能保证则invokeImmediately为false,直接进行线程切换。然后进入dispatch方法,下面是Dispatchers.Main中dispatch的处理逻辑。
override fun dispatch(context: CoroutineContext, block: Runnable) {
handler.post(block)
}
这个再熟悉不过了,因为这个时候的handler.post就是代表向主线程推送消息,此时的block将会在主线程进行调用。
这样线程的切换就完成。
所以综上来看,CoroutineDispatcher为协程提供了一个线程切换的统一判断与执行标准。
- 首先在协程进行启动的时候通过拦截器的方式进行拦截,对应的方法是
interceptContinuation; - 然后返回一个具有切换线程功能的
Continuation,在每次进行resumeWith的时候,内部再通过isDispatchNeeded进行判断当前协程的运行是否需要切换线程。如果需要则调用dispatch进行线程的切换,保证协程的正确运行; - 如果我要自定义协程线程的切换逻辑,就可以通过继承于
CoroutineDispatcher来实现,将它的核心方法进行自定义即可; - 当然,如果你是在
Android中使用协程,那基本上是不需要自定义线程的切换逻辑。因为kotlin已经为我们提供了日常所需的Dispatchers。