通常情况下,使用协程启动job时,job的调度依赖协程上下文指定的派发器,简单说job是调度器异步执行的,协程只是保障启动协程的线程不阻塞
但是!当指定CoroutineContext.Dispatcher为Dispatchers.Main.immediate时,你会发现launch的代码在主线程顺序进行
问题来了
异步调度能理解,把job丢给线程池执行,执行完毕resume即可(需要一点协程原理基础)
同步调度是如何实现的?(其实这是个伪命题,下文剖析)
场景复现
Dispatchers.Default
Log.e(TAG, "first")
GlobalScope.launch {
Log.e(TAG, Thread.currentThread().name + " second")
}
Log.e(TAG, "third")
// 输出
first
third
DefaultDispatcher-worker-1 second
Dispatchers.Main.immediate(以下Dispatchers.Main代指Dispatchers.Main.immediate)
// 继承CoroutineScope, 覆写coroutineContext即可
val coroutineContext: CoroutineContext = Dispatchers.Main.immediate + job + exceptionHandler
Log.e(TAG, "first")
launch {
Log.e(TAG, Thread.currentThread().name + " second")
}
Log.e(TAG, "third")
// 输出
first
main second
third
有点意思,看样子如果Dispatchers.Main执行耗时任务,会阻塞主线程(ANR maybe)?试试
Log.e(TAG, "first")
launch {
Thread.sleep(TimeUnit.SECONDS.toMillis(10))
Log.e(TAG, Thread.currentThread().name + " second")
}
Log.e(TAG, "third")
// 输出
first
// 约10s后输出
main second
third
Decompile一下,invokeSuspend函数并没有什么差异,协程代码生成时没有对Dispatchers.Main进行特殊处理,芭比Q了只能看源码了
Debug看下调度堆栈,定位下源码类(精通协程原理的大佬请忽略)
Dispatchers.Default调用堆栈
Dispatchers.Main调用堆栈
看堆栈已经很清晰了,Dispatchers.Main都没启动Worker(没有调度job的说法),而是同步调用一堆协程生成的代码,所以没必要时不要随便使用MainScope跑主线程代码...
基于堆栈逐步跟进,发现DispatchedContinuation类有个判断
DispatchedContinuation
inline fun resumeCancellableWith(
result: Result<T>,
noinline onCancellation: ((cause: Throwable) -> Unit)?
) {
val state = result.toState(onCancellation)
if (dispatcher.isDispatchNeeded(context)) {
_state = state
resumeMode = MODE_CANCELLABLE
dispatcher.dispatch(context, this)
} else {
// ...
}
}
可以看到,job进入dispatcher执行的前提是dispatcher.isDispatchNeeded(context),Dispatchers.Main中context类型为HandlerContext
HandlerContext
override fun isDispatchNeeded(context: CoroutineContext): Boolean {
return !invokeImmediately || Looper.myLooper() != handler.looper
}
isDispatchNeeded方法返回了false,真相大白
Questions
通过Dispatchers.Main构建的CoroutineScope有什么用?
一般指定为Dispatchers.Main的CoroutineScope又称UiScope/MainScope,我理解主要就是用来在主线程调用suspend函数的,suspend函数里再进行线程切换,resume后切回主线程,适用Init UI -> Request Data -> Refresh UI的场景