前言
默认分发器的runBlocking
fun testBlock() {
log("testBlock")
runBlocking {
log("runBlocking")
Thread.sleep(200)
log("runBlocking end")
}
log("testBlocking end")
}
private fun log(msg: Any?) = println("[${Thread.currentThread()}] $msg")
输出:
[Thread[Test worker,5,main]] testBlock
[Thread[Test worker @coroutine#1,5,main]] runBlocking
[Thread[Test worker @coroutine#1,5,main]] runBlocking end
[Thread[Test worker,5,main]] testBlocking end
协程运行在当前线程,若在协程里执行了耗时函数,那么协程之后的代码只能等待。runBlocking常用于一些测试的场景(谁没事写个协程阻塞当前线程呢)。如果在主线程(UI)runBlocking使用Thread.sleep(10000)会在主线程触发ANR。但替换为协程的delay(10000)不会触发ANR。delay类似于Handler去post一个任务。
runBlocking是自定义返回值的:
fun main() {
val name = runBlocking {
"finish"
}
log("name:$name")
}
输出:
[Thread[Test worker,5,main]] name:finish
# Builders.kt
@Throws(InterruptedException::class)
public actual fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
// 当前线程
val currentThread = Thread.currentThread()
// 先看当前有没有拦截器
val contextInterceptor = context[ContinuationInterceptor]
val eventLoop: EventLoop?
val newContext: CoroutineContext
// loop实际创建的是BlockingEventLoop,最终都是继承于CoroutineDispatcher。协程启动后会构造DispatchedContinuation,然后依靠dispatcher将runable分发执行,而这个dispatcher即是BlockingEventLoop
if (contextInterceptor == null) {
// 不特别指定的话没有拦截器,使用Loop构建Context
eventLoop = ThreadLocalEventLoop.eventLoop
newContext = GlobalScope.newCoroutineContext(context + eventLoop)
} else {
// See if context's interceptor is an event loop that we shall use (to support TestContext)
// or take an existing thread-local event loop if present to avoid blocking it (but don't create one)
eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() }
?: ThreadLocalEventLoop.currentOrNull()
newContext = GlobalScope.newCoroutineContext(context)
}
// 阻塞的协程
val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop)
// 开启
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
// 等待协程执行完成。协程任务已经提交到队列里,就看什么时候取出执行。
return coroutine.joinBlocking()
}
# EventLoop.common.kt
public final override fun dispatch(context: CoroutineContext, block: Runnable) = enqueue(block)
open fun enqueue(task: Runnable) {
// 将task加入队列,task=DispatchedContinuation
if (enqueueImpl(task)) {
// todo: we should unpark only when this delayed task became first in the queue
unpark()
} else {
DefaultExecutor.enqueue(task)
}
}
BlockingEventLoop的父类EventLoopImpIBase成员变量_queue(队列),存储提交的任务。
@Suppress("UNCHECKED_CAST")
fun joinBlocking(): T {
registerTimeLoopThread()
try {
eventLoop?.incrementUseCount()
try {
while (true) {
// 当前线程已经中断,直接退出
if (Thread.interrupted()) throw InterruptedException().also { cancelCoroutine(it) }
// 如果eventLoop!=null,从队列取出task执行
val parkNanos = eventLoop?.processNextEvent() ?: Long.MAX_VALUE
// 协程执行结束,跳出循环
if (isCompleted) break
// 挂起线程,parkNanos挂起时间
parkNanos(this, parkNanos)
}
} finally { // paranoia
eventLoop?.decrementUseCount()
}
} finally { // paranoia
unregisterTimeLoopThread()
}
// now return result
val state = this.state.unboxState()
(state as? CompletedExceptionally)?.let { throw it.cause }
// 返回结果
return state as T
}
public open fun processNextEvent(): Long {
if (!processUnconfinedEvent()) return Long.MAX_VALUE
return 0
}
public fun processUnconfinedEvent(): Boolean {
// 队列
val queue = unconfinedQueue ?: return false
// 从队列取出task
val task = queue.removeFirstOrNull() ?: return false
task.run()
return true
}
尝试从队列里取出task,若没有则挂起线程。
- 先创建协程,包装为DispatchedContinuation,作为task
- 分发task,将task加入队列里
- 从队列里取出task执行,实际执行的即协程体
- 当执行完毕了,runBlocking函数也就退出了。
构造协程->包装为task->BlockingEventLoop分发->加入阻塞队列
从队列里取出task执行->执行协程体->runBlocking结束
指定分发器的runBlocking
fun main() {
log("main")
runBlocking(Dispatchers.IO) {
log("runBlocking")
Thread.sleep(2000)
println("runBlocking end")
}
log("main end")
}
输出:
[Thread[Test worker,5,main]] main
[Thread[DefaultDispatcher-worker-1 @coroutine#1,5,main]] runBlocking
runBlocking end
[Thread[Test worker,5,main]] main end
指定在子线程里进行分发。
默认分发器加入队列、取出队列都是在同一个线程,而指定分发器后task不会加入到队列里,task的调度执行完全由指定的分发器完成。
coroutine.joinBlocking后,当前线程一定会被挂起。等到协程执行完毕后再唤醒当前被挂起的线程。唤醒处在于:
#Builders.kt
override fun afterCompletion(state: Any?) {
// wake up blocked thread
if (Thread.currentThread() != blockedThread)
// blockedThread即为调用coroutine.joinBlocking后阻塞的线程
// Thread.currentThread 为线程池的线程
// 唤醒线程
unpark(blockedThread)
}
构造协程->包装为task->线程池分发->子线程执行->线程池执行Task->执行协程体
执行协程体的过程中父线程阻塞等待->协程体执行结束 唤醒父线程->runBlocking结束
不管是否指定分发器,runBlocking都会阻塞等待协程执行完毕。
launch 使用与原理
fun main() {
var job = GlobalScope.launch {
log("launch")
// 协程里不建议用Thread.sleep。建议用协程的delay
Thread.sleep(200)
log("launch end")
}
log("main end")
// 防止父线程过早执行结束,导致子线程得不到执行
Thread.sleep(300)
}
输出:
[Thread[Test worker,5,main]] main end
[Thread[DefaultDispatcher-worker-1 @coroutine#1,5,main]] launch
[Thread[DefaultDispatcher-worker-1 @coroutine#1,5,main]] launch end
launch并不阻塞当前线程。
构造协程->包装task->线程池分发->子线程执行->线程池执行task->执行协程体
join使用和原理
我们需要知道协程执行完毕没用Job.join。是挂起函数(当前线程不阻塞)
#Job.kt
public suspend fun join()
#JobSupport.kt
public final override suspend fun join() {
// 快速判断状态不耗时
if (!joinInternal()) { // fast-path no wait
coroutineContext.ensureActive()
return // do not suspend
}
// 挂起的地方
return joinSuspend() // slow-path wait
}
// 封装后的协程。suspendCancellableCoroutine 典型的挂起操作
private suspend fun joinSuspend() = suspendCancellableCoroutine<Unit> { cont ->
// 执行完就挂起
cont.disposeOnCancellation(invokeOnCompletion(handler = ResumeOnCompletion(cont).asHandler))
}
suspendCancellableCoroutine是挂起的核心。
joinSuspend函数,将当前协程体存储到Job的state里(作为node)。将当前协程挂起。 协程执行完后恢复。
private class ResumeOnCompletion(
private val continuation: Continuation<Unit>
) : JobNode() {
// continuation 为协程的包装体,里面有我们真正的协程体。之后重新进行分发
override fun invoke(cause: Throwable?) = continuation.resume(Unit)
}
fun main() {
runBlocking {
var job = GlobalScope.launch {
log("launch")
// 协程里不建议用Thread.sleep。建议用协程的delay
Thread.sleep(200)
log("launch end")
}
// 检测job没有执行完成;将协程体(runBlocking{}里的内容)放入job的state里;挂起协程;
job.join()
// job 完成后继续执行下面的代码
log("main end")
// 防止父线程过早执行结束,导致子线程得不到执行
Thread.sleep(300)
}
}
输出:
[Thread[DefaultDispatcher-worker-1 @coroutine#2,5,main]] launch
[Thread[DefaultDispatcher-worker-1 @coroutine#2,5,main]] launch end
[Thread[Test worker @coroutine#1,5,main]] main end
当协程执行完毕,会例行检查当前的state是否有挂着需要执行的node,刚好我们在joinSuspend里放了node,找到之前的协程体再次进行分发。根据协程状态机的知识可知,第二次执行协程体,因此肯定会执行job。join之后的代码。
job执行完成->寻找node重新分发执行(runBlocking内容)->根据协程状态,把剩余的runBlocking运行完成。
检测job没有执行完成->将协程体(runBlocking{}里的内容)放入job的state里。挂起协程
async/await使用和原理
launch协程执行没有返回值
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
// 返回值是Unit,没有返回值
block: suspend CoroutineScope.() -> Unit
): Job
用async/await带返回值。
fun main() {
runBlocking {
// 启动协程
val job = GlobalScope.async {
log("async")
Thread.sleep(200)
// 返回值
"finish"
}
// 等待协程执行结束,并返回协程结果
val result = job.await()
log("result:$result")
}
}
输出:
[Thread[DefaultDispatcher-worker-1 @coroutine#2,5,main]] async
[Thread[Test worker @coroutine#1,5,main]] result:finish
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
// 定义泛型的返回值
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val newContext = newCoroutineContext(context)
// 构造DeferredCoroutine延迟给结果的协程
val coroutine = if (start.isLazy)
LazyDeferredCoroutine(newContext, block) else
DeferredCoroutine<T>(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
先判断是否需要挂起,若线程已经结束/被取消,就无需等待直接返回。先将当前协程体包装到state里作为node存放,然后挂起协程。等待async里的写执行完成,再重新调度执行await之后的代码,此时协程的值已经返回。
经过分发,在子线程里执行invokeSuspend->实际执行的是协程体内容->因为await挂起了,在子协程结束时许哟啊恢复外部协程执行->执行runBlocking协程体->completion.resumeWith(outcome)此处的outCome="finish"->将结果存到result中.
外层runBlocking体会执行2次。
- 调用invokeSuspend(xx),此时参数xx=Unit,后遇到await被挂起
- 子协程(线程池重新创建一个线程)执行结束并返回结果"finish",恢复外部协程时再次调用invokeSuspend(xx),此时参数xx="finish",并保存参数,result就有了值。
async启动的协程,若协程发生了异常,不会像launch那样直接抛出,而是需要等待await时抛出。
delay使用与原理
线程可以被阻塞,协程可以被挂起,挂起后的协程等待机会成熟可以被恢复。
fun main() {
GlobalScope.launch {
log("launch")
// 当协程执行到挂起函数这里,等待获取名字后再恢复运行
var name = getUserName()
log("name:$name")
}
// 防止launch协程得不到执行父线程就退出了
Thread.sleep(2500)
}
// 挂起函数
private suspend fun getUserName(): String {
return withContext(Dispatchers.IO) {
// 模拟网络获取
Thread.sleep(2000)
"fish"
}
}
输出:
[Thread[DefaultDispatcher-worker-1 @coroutine#1,5,main]] launch
[Thread[DefaultDispatcher-worker-2 @coroutine#1,5,main]] name:fish
协程挂起一段时间可用delay(xx)函数
fun main() {
GlobalScope.launch {
log("launch")
delay(5000)
log("launch end")
}
// 防止launch协程得不到执行父线程就退出了
Thread.sleep(5100)
}
输出:
[Thread[DefaultDispatcher-worker-1 @coroutine#1,5,main]] launch
[Thread[DefaultDispatcher-worker-1 @coroutine#1,5,main]] launch end
public suspend fun delay(timeMillis: Long) {
// 没必要延迟时
if (timeMillis <= 0) return
return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
// if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.
// 封装协程为cont,便于之后恢复
if (timeMillis < Long.MAX_VALUE) {
// 核心实现
cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
}
}
}
#DefaultExecutor.kt
internal actual val DefaultDelay: Delay = kotlinx.coroutines.DefaultExecutor
//单例
internal actual object DefaultExecutor : EventLoopImplBase(), Runnable {
const val THREAD_NAME = "kotlinx.coroutines.DefaultExecutor"
//...
private fun createThreadSync(): Thread {
return DefaultExecutor._thread ?: Thread(this, DefaultExecutor.THREAD_NAME).apply {
DefaultExecutor._thread = this
isDaemon = true
start()
}
}
//...
override fun run() {
//循环检测队列是否有内容需要处理
//决定是否要挂起线程
}
//...
}
DefaultExecutor是单例,开启线程,检测队列里任务的情况来决定是否需要挂起线程等待。
放入队列:DefaultExecutor继承自EventLoopImplBase,在最开始分析runBlocking时它里面的_queue存储队列元素,成员变量_delayed
#EventLoop.common.kt
internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay {
//存放正常task
private val _queue = atomic<Any?>(null)
//存放延迟task
private val _delayed = atomic<EventLoopImplBase.DelayedTaskQueue?>(null)
}
private inner class DelayedResumeTask(
nanoTime: Long,
private val cont: CancellableContinuation<Unit>
) : EventLoopImplBase.DelayedTask(nanoTime) {
//协程恢复
override fun run() { with(cont) { resumeUndispatched(Unit) } }
override fun toString(): String = super.toString() + cont.toString()
}
delay.scheduleResumeAfterDelay本质是创建task:DelayedResumeTask,并将该task加入到延迟队列_delayed里。
从队列取出:DefaultExecutor调用processNextEvent函数检测队列是否有数据,如果没有则将线程挂起一段时间(由processNextEvent返回值确定)。
##EventLoop.common.kt
override fun processNextEvent(): Long {
if (processUnconfinedEvent()) return 0
val delayed = _delayed.value
if (delayed != null && !delayed.isEmpty) {
//调用delay 后会放入
//查看延迟队列是否有任务
val now = nanoTime()
while (true) {
//一直取任务,直到取不到(时间未到)
delayed.removeFirstIf {
//延迟任务时间是否已经到了
if (it.timeToExecute(now)) {
//将延迟任务从延迟队列取出,并加入到正常队列里
enqueueImpl(it)
} else
false
} ?: break // quit loop when nothing more to remove or enqueueImpl returns false on "isComplete"
}
}
// 从正常队列里取出
val task = dequeue()
if (task != null) {
//执行
task.run()
return 0
}
//返回线程需要挂起的时间
return nextTime
}
执行任务最终执行DelayedResumeTask.run函数,该函数对协程进行恢复。
- 构造task加入延迟队列,此时协程挂起
- 有个单独的线程会检测是否需要取出task并执行,没到时间就哟啊挂起等待
- 时间到了从延迟队列里取出并放入正常的队列,从正常队列取出并执行
- task执行的过程就是协程恢复的过程
delay单例开启一个线程->检测队列状态,决定线程是否挂起->挂起时间结束->检测延迟任务需要执行->延迟队列->从延迟队列取出,并加入正常队列->正常队列->从队列取出任务,任务核心:恢复执行协程
delay构造延迟任务,并加入延迟队列里->delay结束,协程挂起(不阻塞当前线程)