【Kotlin回顾】18.Kotlin协程—Dispatcher

364 阅读11分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第18天,点击查看活动详情

  • 协程是如何运行的
  • 协程创建出来后如何跟线程产生关联

通过前面的分析已经知道协程是如何创建出来的,那么协程创建出来后又是如何运行的呢?协程创建出来后又是如何与线程产生关联的?

1.Dispatcher是什么

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
}

在创建一个协程的时候需要一个CoroutineContext参数,这个参数不是必传的,因为它有默认值EmptyCoroutineContext,Kotlin官方用它来替代了null,这是Kotlin空安全思维。这个参数也可以自行传递,例如前面的代码可以这么写:

fun coroutineTest() {
    val scope = CoroutineScope(Job())

    val block: suspend CoroutineScope.() -> Unit = {
        println("Hello")
        delay(1000L)
        println("Kotlin")
    }

//					 改了这里
//					   ↓
    scope.launch(Dispatchers.Default, block = block)
}

Dispatchers.Default替代了默认的EmptyCoroutineContext,那么这里的Dispatchers的定义跟CoroutineContext有什么关系呢?

/**
* 对[CoroutineDispatcher]的各种实现进行分组。
*/
public actual object Dispatchers {

	/**
     * 用于CPU密集型任务的线程池,一般来说它内部的线程个数是与机器 CPU 核心数量保持一致的
     * 不过它有一个最小限制2,
     */
    public actual val Default: CoroutineDispatcher = DefaultScheduler

    /**
     * 主线程,在Android中才可以使用,主要用于UI的绘制,在普通JVM上无法使用
     */
    public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher

    /**
     * 不局限于任何特定线程,会根据运行时的上下文环境决定
     */
    public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined

    /**
     * 用于执行IO密集型任务的线程池,它的数量会多一些,默认最大线程数量为64个
     * 具体的线程数量可以通过kotlinx.coroutines.io.parallelism配置
     * 它会和Default共享线程,当Default还有其他空闲线程时是可以被IO线程池复用。
     */
    public val IO: CoroutineDispatcher = DefaultIoScheduler
}

Dispatchers是一个单例对象,它里面的几个类型都是CoroutineDispatcher。

/**
 * 所有协程调度器实现扩展的基类。
 */
public abstract class CoroutineDispatcher :
    AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { }

/**
 * 标记拦截协程延续的协程上下文元素。
 */
public interface ContinuationInterceptor : CoroutineContext.Element { }

/**
 * CoroutineContext的一个元素。协程上下文的一个元素本身就是一个单例上下文。
 */
public interface Element : CoroutineContext { }

CoroutineDispatcher本身又是CoroutineContext,从源码分析可以得出他们的关系可以这么表示:

2.newCoroutineContext(context)

Dispatcher的关系分析完成之后进入到launch函数中的第一行代码newCoroutineContext(context),这一行代码完成了哪些工作呢?

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
}
/**
 * 为新的协程创建上下文。当没有指定其他调度程序或[ContinuationInterceptor]时,
 * 它会设置[Dispatchers.Default],并添加对调试工具的可选支持(当打开时)。
 */
public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
    //①
	val combined = coroutineContext.foldCopiesForChildCoroutine() + 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,这是因为newCoroutineContext是CoroutineScope的扩展函数,CoroutineScope对CoroutineContext进行了封装,所以newCoroutineContext函数中可直接访问CoroutineScope的coroutineContext;foldCopiesForChildCoroutine函数返回子协程要继承的[CoroutineContext];然后跟传入的context参数进行合并。这行代码就是让子协程可以继承父协程的上下文元素。
  • 注释②:它的作用是在调试模式下,为我们的协程对象增加唯一的 ID,这个ID就是在调试协程程序时出现的日志如:Thread:DefaultDispatcher-worker-1 @coroutine#1中的@coroutine#1,其中的1就是这个ID。
  • 注释③:如果合并后的combined没有指定调度程序就默认使用Dispatcher.Default。

通过上面的分析可以得出newCoroutineContext函数确定了默认使用的线程池是Dispatcher.Default那么这里为什么会默认使用Default线程池而不是Main呢? 因为Kotlin并不是只针对Android开发的,它支持多个平台Main线程池仅在UI相关的平台中才会用到,而协程是不能脱离线程运行的,所以这里默认使用Default线程池。

3.Dispatcher做了什么

现在再分析协程创建后会调用intercepted函数

public fun <T> (suspend () -> T).startCoroutineCancellable(completion: Continuation<T>): Unit = runSafely(completion) {
	//												看这里
	//												 ↓
	createCoroutineUnintercepted(completion).intercepted().resumeCancellableWith(Result.success(Unit))
}

//需要找到对应平台的具体实现
public expect fun <T> Continuation<T>.intercepted(): Continuation<T>

//JVM平台的实现
//IntrinsicsJvm.kt#intercepted
/**
 * 使用[ContinuationInterceptor]拦截continuation。
 */
public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
    (this as? ContinuationImpl)?.intercepted() ?: this

在分析协程的创建过程中已经分析过上面的this代表的就是block变量,所以这里的强转是成立的,那么这里的intercepted调用的就是ContinuationImpl对象中的函数

/**
 * 命名挂起函数的状态机扩展自这个类
 */
internal abstract class ContinuationImpl(
    completion: Continuation<Any?>?,
    private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {
    constructor(completion: Continuation<Any?>?) : this(completion, completion?.context)

    public override val context: CoroutineContext
        get() = _context!!

    @Transient
    private var intercepted: Continuation<Any?>? = null

	//重点看这里
    public fun intercepted(): Continuation<Any?> =
        intercepted
            ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
                .also { intercepted = it }

}

首先intercepted()方法会判断它的成员变量intercepted是否为空,如果为空则调用context[ContinuationInterceptor]获取上下文当中的Dispatcher对象,通过前面的分析在协程中默认的是Default线程池,因此这里进入的就是Default线程池。

通过Debug进入到CoroutineDispatcher中的interceptContinuation函数

public abstract class CoroutineDispatcher :
    AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {

	/**
	 * 返回一个封装了提供的[continuation]的continuation,从而拦截所有的恢复。
	 * 这个方法通常应该是异常安全的。
	 * 从此方法抛出的异常可能会使使用此调度程序的协程处于不一致且难以调试的状态。
	 */
	public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
        DispatchedContinuation(this, continuation)
}

interceptContinuation返回了一个DispatchedContinuation对象,其中的this就是默认的线程池Dispatchers.Default。

然后通过DispatchedContinuation调用它的resumeCancellableWith()函数。

3.协程是如何与线程产生关联的

协程与线程产生关联的地方还是要从协程创建之后看起也就是resumeCancellableWith,通过上面的分析这个函数就是intercepted()的返回值DispatchedContinuation中的函数。

public fun <T> (suspend () -> T).startCoroutineCancellable(completion: Continuation<T>): Unit = runSafely(completion) {
//																现在看这里
//																	↓
	createCoroutineUnintercepted(completion).intercepted().resumeCancellableWith(Result.success(Unit))
}

internal class DispatchedContinuation<in T>(
	@JvmField val dispatcher: CoroutineDispatcher,
	@JvmField val continuation: Continuation<T>
) : DispatchedTask<T>(MODE_UNINITIALIZED), CoroutineStackFrame, Continuation<T> by continuation {

...

public fun <T> Continuation<T>.resumeCancellableWith(
	result: Result<T>,
	onCancellation: ((cause: Throwable) -> Unit)? = null
): Unit = when (this) {
	is DispatchedContinuation -> resumeCancellableWith(result, onCancellation)
	else -> resumeWith(result)
}

//我们内联它来保存堆栈上的一个条目,在它显示的情况下(无限制调度程序)
//它只在Continuation<T>.resumeCancellableWith中使用
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 {
			executeUnconfined(state, MODE_CANCELLABLE) {
				if (!resumeCancelled(state)) {
					resumeUndispatchedWith(result)
				}
			}
		}
	}
	...
}

DispatchedContinuation继承了DispatchedTask:

internal abstract class DispatchedTask<in T>(
    @JvmField public var resumeMode: Int
) : SchedulerTask() { }

internal actual typealias SchedulerTask = Task

internal abstract class Task(
    @JvmField var submissionTime: Long,
    @JvmField var taskContext: TaskContext
) : Runnable {
    constructor() : this(0, NonBlockingContext)
    inline val mode: Int get() = taskContext.taskMode // TASK_XXX
}

DispatchedTask继承了SchedulerTask,同时SchedulerTask还是Task的别名,Task又实现了Runnable接口,这意味着它可以被分发到Java的线程中去执行了。

同时可以得出一个结论:DispatchedContinuation是一个Runnable。

DispatchedContinuation还实现了Continuation接口,它还使用了类委托的语法将接口的具体实现交给了它的成员属性continuation,那么这里对上面的结论进行补充:DispatchedContinuation不仅是一个Runnable,还是一个Continuation。

DispatchedContinuation分析完了进入它的resumeCancellableWith函数分析:

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 {
		//这里就是Dispatchers.Unconfined情况,这个时候协程不会被分发到别的线程,只运行在当前线程中。
		executeUnconfined(state, MODE_CANCELLABLE) {
			if (!resumeCancelled(state)) {
				resumeUndispatchedWith(result)
			}
		}
	}
}
  • 注释①: dispatcher来自CoroutineDispatcher,isDispatchNeeded就是它的成员函数
public abstract class CoroutineDispatcher :
AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {

	/**
	 * 如果协程的执行应该使用[dispatch]方法执行,则返回' true '。
	 * 大多数dispatchers的默认行为是返回' true '。
	 */
	public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true
	
}

isDispatchNeeded默认返回true,且在大多数情况下都是true, 但是也有个例外就是在它的子类中Dispatchers.Unconfined 会将其重写成 false。

internal object Unconfined : CoroutineDispatcher() { 
	// 只有Unconfined会重写成false 
	override fun isDispatchNeeded(context: CoroutineContext): Boolean = false
}

因为默认是true,所以接下来会进入注释②

  • 注释②: 注释②调用了CoroutineDispatcher中的dispatch方法将block代码块调度到另一个线程上,这里的线程池默认值是Dispatchers.Default所以任务被分发到Default线程池,第二个参数是Runnable,这里传入的是this,因为DispatchedContinuation间接的实现了Runnable接口。
public abstract class CoroutineDispatcher :
AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {

	/**
	 * 在给定的[上下文]中,将一个可运行的block调度到另一个线程上。
	 * 这个方法应该保证给定的[block]最终会被调用,否则系统可能会达到死锁状态并且永远不会终止。
	 */ 
	public abstract fun dispatch(context: CoroutineContext, block: Runnable)
}

因为默认线程池是Dispatchers.Default,所以这里的dispatch其实调用的是Dispatchers.Default.dispatch,这里的Dispatchers.Default的本质是一个单例对象DefaultScheduler, 它继承了SchedulerCoroutineDispatcher:

//继承了SchedulerCoroutineDispatcher
internal object DefaultScheduler : SchedulerCoroutineDispatcher(
    CORE_POOL_SIZE, MAX_POOL_SIZE,
    IDLE_WORKER_KEEP_ALIVE_NS, DEFAULT_SCHEDULER_NAME
) {
    // 关闭调度程序,仅用于Dispatchers.shutdown()
    internal fun shutdown() {
        super.close()
    }

    //重写 (Dispatchers.Default as ExecutorCoroutineDispatcher).close()
    override fun close() {
        throw UnsupportedOperationException("Dispatchers.Default cannot be closed")
    }

    override fun toString(): String = "Dispatchers.Default"
}

internal open class SchedulerCoroutineDispatcher(
    private val corePoolSize: Int = CORE_POOL_SIZE,
    private val maxPoolSize: Int = MAX_POOL_SIZE,
    private val idleWorkerKeepAliveNs: Long = IDLE_WORKER_KEEP_ALIVE_NS,
    private val schedulerName: String = "CoroutineScheduler",
) : ExecutorCoroutineDispatcher() {

	private var coroutineScheduler = createScheduler()
	
	override fun dispatch(context: CoroutineContext, block: Runnable): Unit = coroutineScheduler.dispatch(block)
	
}

SchedulerCoroutineDispatcher中实际调用dispatch方法的实际是coroutineScheduler,所以dispatcher.dispatch实际调用的是coroutineScheduler.dispatch()

internal class CoroutineScheduler(
    @JvmField val corePoolSize: Int,
    @JvmField val maxPoolSize: Int,
    @JvmField val idleWorkerKeepAliveNs: Long = IDLE_WORKER_KEEP_ALIVE_NS,
    @JvmField val schedulerName: String = DEFAULT_SCHEDULER_NAME
) : Executor, Closeable {

	//Executor接口中的方法被覆盖
	override fun execute(command: Runnable) = dispatch(command)
	
	fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, tailDispatch: Boolean = false) {
        trackTask() 
		//将传入的 Runnable 类型的 block(也就是 DispatchedContinuation),包装成 Task。
        val task = createTask(block, taskContext)
		// 拿到当前的任务队列, 尝试将任务提交到本地队列并根据结果进行操作
		//Worker其实是一个内部类,其实就是Java的Thread类
        val currentWorker = currentWorker()
		//将当前的 Task 添加到 Worker 线程的本地队列,等待执行。
        val notAdded = currentWorker.submitToLocalQueue(task, tailDispatch)
        if (notAdded != null) {
            if (!addToGlobalQueue(notAdded)) {
                // 全局队列在关闭/关闭的最后一步关闭——不再接受任何任务
                throw RejectedExecutionException("$schedulerName was terminated")
            }
        }
        val skipUnpark = tailDispatch && currentWorker != null
        // 
        if (task.mode == TASK_NON_BLOCKING) {
            if (skipUnpark) return
            signalCpuWork()
        } else {
            //增加阻塞任务 
            signalBlockingWork(skipUnpark = skipUnpark)
        }
    }
}

Worker是什么?

 internal inner class Worker private constructor() : Thread() {
	  init {
            isDaemon = true		//守护线程默认为true
        }

	  private fun runWorker() {
            var rescanned = false
            while (!isTerminated && state != WorkerState.TERMINATED) {
				//在while循环中一直尝试从队列中找到任务
                val task = findTask(mayHaveLocalTasks)
                // 找到任务则进行下一步
                if (task != null) {
                    rescanned = false
                    minDelayUntilStealableTaskNs = 0L
					//执行任务
                    executeTask(task)
                    continue
                } else {
                    mayHaveLocalTasks = false
                }

				//
                if (minDelayUntilStealableTaskNs != 0L) {
                    if (!rescanned) {
                        rescanned = true
                    } else {
                        rescanned = false
                        tryReleaseCpu(WorkerState.PARKING)
                        interrupted()
                        LockSupport.parkNanos(minDelayUntilStealableTaskNs)
                        minDelayUntilStealableTaskNs = 0L
                    }
                    continue
                }

				//没有任务则停止执行,线程可能会关闭
                tryPark()
            }
            tryReleaseCpu(WorkerState.TERMINATED)
        }
 }

任务被找到后是如何执行的?

internal inner class Worker private constructor() : Thread() {
	private fun executeTask(task: Task) {
		val taskMode = task.mode
		//当它找到一个任务时,这个工作者就会调用它
		idleReset(taskMode)
		beforeTask(taskMode)
		runSafely(task)
		afterTask(taskMode)
	}

	fun runSafely(task: Task) {
        try {
            task.run()
        } catch (e: Throwable) {
            val thread = Thread.currentThread()
            thread.uncaughtExceptionHandler.uncaughtException(thread, e)
        } finally {
            unTrackTask()
        }
    }
}


internal abstract class Task(
    @JvmField var submissionTime: Long,
    @JvmField var taskContext: TaskContext
) : Runnable {
    constructor() : this(0, NonBlockingContext)
    inline val mode: Int get() = taskContext.taskMode // TASK_XXX
}

最终进入到runSafely()函数中,然后调用run方法,前面分析过,将DispatchedContinuation包装成一个实现了Runnable接口的Task,所以这里的task.run()本质上就是调用的Runnable.run(),到这里任务就协程任务就真正的执行了。

那么也就可以知道这里的run() 函数其实调用的就是DispatchedContinuation父类DispatchedTask中的run()函数:

internal abstract class DispatchedTask<in T>(
    @JvmField public var resumeMode: Int
) : SchedulerTask() {

	public final override fun run() {
        assert { resumeMode != MODE_UNINITIALIZED } 
        val taskContext = this.taskContext
        var fatalException: Throwable? = null
        try {
            val delegate = delegate as DispatchedContinuation<T>
            val continuation = delegate.continuation
            withContinuationContext(continuation, delegate.countOrElement) {
                val context = continuation.context
                val state = takeState() // 
                val exception = getExceptionalResult(state)
                /*
                 * 检查延续最初是否在异常情况下恢复。
				 * 如果是这样,它将主导取消,否则原始异常将被静默地丢失。
                 */
                val job = if (exception == null && resumeMode.isCancellableMode) context[Job] else null
                if (job != null && !job.isActive) {
					//①
                    val cause = job.getCancellationException()
                    cancelCompletedResult(state, cause)
                    continuation.resumeWithStackTrace(cause)
                } else {
                    if (exception != null) {
						//②
                        continuation.resumeWithException(exception)
                    } else {
						//③
                        continuation.resume(getSuccessfulResult(state))
                    }
                }
            }
        } catch (e: Throwable) {
            // 
            fatalException = e
        } finally {
            val result = runCatching { taskContext.afterTask() }
            handleFatalException(fatalException, result.exceptionOrNull())
        }
    }
	
}
  • 注释①: 在代码执行之前这里会判断当前协程是否被取消。如果被取消了就会调用 continuation.resumeWithStackTrace(cause) 将具体的原因传出去;
  • 注释②: 判断协程是否发生了异常如果已经发生异常就调用continuation.resumeWithException(exception)将异常传递出去;
  • 注释③: 如果前面运行没有问题,就进入最后一步continuation.resume(getSuccessfulResult(state)),此时协程正式被启动并且执行launch当中传入的block或者Lambda函数。

4.总结

  • createCoroutineUnintercepted(completion) 创建了协程的 Continuation 实例,紧接着就会调用它的 intercepted() 方法,将其封装成 DispatchedContinuation 对象,DispatchedContinuation是Runnable的子类,所以也可以说DispatchedContinuation就是Runnable;
  • DispatchedContinuation会持有CoroutineDispatcher以及前面创建的Continuation对象;
  • resumeCancellableWith()方法执行的时候进入到DispatchedContinuation中执行dispatched.dispatch(),这里会将协程的Continuation包装成Task并添加到Worker的本地任务队列等待执行。而这里的Worker本质上是Java中的Thread,在这一步协程完成了线程的切换;
  • 任务添加到Worker的本地任务队列后就会通过run()方法启动任务,这里调用的是task.run(),这里的run最终是调用的DispatchedContinuation的父类DispatchedTask中的run()方法,在这个run方法中如果前面没有异常最终会调用continuation.resume(),然后就开始执行执行协程体中的代码了也就是反编译代码中的invokeSuspend(),这里开始了协程状态机流程。