当前位置: 首页 > news >正文

Android-重学kotlin(协程源码第二阶段)新学习总结

一、Dispatchers是如何工作的?

概述:

        CoroutineContext 是容器,ContinuationInterceptor 是其中的关键元素接口,CoroutineDispatcher 是该接口的主要实现,而 Dispatchers 是预定义调度器的工具类。

1.CoroutineContext

        CoroutineContext 是一个不可变的元素集合,每个元素都实现了 Element 接口。它的核心作用是存储和传递协程的上下文信息(如调度器、Job、异常处理器等)。

public interface CoroutineContext {// 通过Key获取上下文中的元素public operator fun <E : Element> get(key: Key<E>): E?// 合并两个上下文public operator fun plus(context: CoroutineContext): CoroutineContext = ...// 遍历上下文中的所有元素public fun <R> fold(initial: R, operation: (R, Element) -> R): R// 其他方法...// 上下文中元素的Key接口public interface Key<E : Element>// 上下文元素的基接口public interface Element : CoroutineContext {public val key: Key<*>}
}
  • CoroutineContext 是一个接口,其默认实现是 CombinedContext(链表结构)或 EmptyCoroutineContext
  • 每个元素都有唯一的 Key,用于快速查找和替换。

2.ContinuationInterceptor 

ContinuationInterceptor 是 CoroutineContext 中的一种特殊元素,负责拦截协程的执行并改变其调度方式。

public interface ContinuationInterceptor : CoroutineContext.Element {companion object Key : CoroutineContext.Key<ContinuationInterceptor>// 拦截并返回一个新的Continuationpublic fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
}

 它的唯一实现是 CoroutineDispatcher,即调度器是拦截器的具体实现。通过 interceptContinuation 方法,可以在协程恢复执行时插入自定义逻辑(如线程切换)。

3.CoroutineDispatcher 

CoroutineDispatcher 继承自 ContinuationInterceptor,负责决定协程代码块在哪个线程或线程池执行。

public abstract class CoroutineDispatcher : ContinuationInterceptor {override val key: CoroutineContext.Key<*> get() = ContinuationInterceptor// 判断是否需要调度(即是否需要切换线程)public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true// 将任务分派到指定线程执行public abstract fun dispatch(context: CoroutineContext, block: Runnable)// 拦截协程的恢复操作,实现线程切换override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =DispatchedContinuation(this, continuation)
}
  • dispatch 方法是核心,不同的调度器(如 Main、IO、Default)通过不同的实现决定任务执行位置。
  • DispatchedContinuation 是一个包装类,负责在协程恢复时调用调度器的 dispatch 方法。

 Dispatchers 是一个单例对象,提供常用的调度器实例,方便开发者直接使用。

public actual object Dispatchers {// 主调度器(用于UI线程,如Android的MainLooper)public actual val Main: CoroutineDispatcher get() = MainDispatcherLoader.dispatcher// 默认调度器(用于CPU密集型任务,默认使用JVM的公共线程池)public actual val Default: CoroutineDispatcher = createDefaultDispatcher()// IO调度器(用于IO密集型任务,使用弹性线程池)public actual val IO: CoroutineDispatcher = DefaultIoScheduler// 未受限调度器(立即在当前线程执行,直到第一个挂起点)public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
}
  • 这些调度器都是 CoroutineDispatcher 的具体实现。
  • 通过 Dispatchers 可以快速获取预定义的调度器,也可以自定义调度器。

阶段总结:

        Kotlin协程调度机制的工作流程为:首先通过`Dispatchers`工具类获取预定义的`CoroutineDispatcher`实例(如`Dispatchers.IO`),并与其他上下文元素(如`Job`)组合成`CoroutineContext`;启动协程时,协程构建器(如`launch`)将此上下文与协程绑定,其中的`ContinuationInterceptor`(由`CoroutineDispatcher`实现)会拦截协程的`Continuation`,注入调度逻辑;

        调度器通过`isDispatchNeeded()`判断是否需要切换线程,若需要则调用`dispatch()`将任务提交到目标线程/线程池(如IO线程池或UI线程);协程执行到挂起点时保存状态暂停执行,恢复时再次由调度器判断并处理线程切换;

        协程结束后,相关资源(如线程池中的空闲线程)会被自动清理,整个过程通过上下文和调度器的协同工作,实现了协程在不同执行环境间的高效切换与管理。

 整体关系图:

CoroutineContext (容器)
├─ Job (协程的生命周期管理)
├─ CoroutineName (协程名称)
└─ ContinuationInterceptor (拦截器接口)└─ CoroutineDispatcher (调度器实现)├─ Dispatchers.Main (UI线程调度器)├─ Dispatchers.Default (默认调度器)├─ Dispatchers.IO (IO调度器)└─ Dispatchers.Unconfined (未受限调度器)

二、CoroutineScope是如何管理协程的?

1. CoroutineScope 的创建:强制绑定 Job 元素

CoroutineScope的核心特性是确保其coroutineContext中必然存在 Job 元素,这是管理协程的基础。源码中,CoroutineScope的创建通常通过两种方式保证 Job 存在:

  • 显式传入包含 Job 的上下文,如CoroutineScope(Job() + Dispatchers.Main)
  • 若传入的上下文不含 Job,会自动添加默认 Job(如GlobalScope的实现中,coroutineContext默认包含EmptyCoroutineContext + Job())。

其接口定义明确依赖coroutineContext,而 Job 作为上下文的强制元素,成为 Scope 管理协程的 “根节点”:

public interface CoroutineScope {public val coroutineContext: CoroutineContext // 必然包含Job
}

 2. 协程创建:构建 Job 的 N 叉树结构

当通过launchasync等构建器(CoroutineScope的扩展函数)创建协程时,会触发以下源码逻辑,形成 Job 的层级关系:

  • 创建协程实例:构建器会实例化AbstractCoroutine的子类(如StandaloneCoroutineDeferredCoroutine),这些子类是协程的具体载体,内部持有一个Job实例(子 Job)。
  • 绑定父子 Job:在AbstractCoroutineinitParentJob()方法中,会将子 Job 与CoroutineScope的 Job(根 Job)或上级协程的 Job(父 Job)绑定,核心逻辑如下:
internal abstract class AbstractCoroutine<in T>(override val context: CoroutineContext,active: Boolean
) : JobSupport(active), Continuation<T>, CoroutineStackFrame {init {// 初始化父Job关系initParentJob(context[Job]) }private fun initParentJob(parent: Job?) {if (parent != null) {// 将当前Job(子Job)注册到父Job中,形成父子关联parent.attachChild(this) }}
}

每个父 Job 会通过attachChild维护一个子 Job 列表,最终所有协程的 Job 以CoroutineScope的根 Job 为顶点,形成N 叉树结构(根 Job→子 Job→孙 Job...)。

3. 取消与异常传播:基于树结构的递归机制

Job 的 N 叉树结构决定了取消事件和异常的传播规则,源码中通过JobSupport(Job 的核心实现类)的事件通知机制实现:

  • 父 Job 取消:向下递归取消所有子 Job
    CoroutineScope的根 Job 被取消(如scope.cancel()),会触发JobSupport.cancel()方法,该方法会遍历所有子 Job 并调用其cancel(),形成递归:

internal open class JobSupport(...) : Job {override fun cancel(cause: CancellationException?) {// 标记当前Job为取消状态val token = markCancelled(cause)if (token != null) {// 遍历所有子Job,触发取消cancelChildren(cause, token) // 通知父Job当前Job已取消(向上传播)parent?.childCancelled(this, cause)}}
}

        子 Job 异常:区分异常类型的双向传播
当子 Job 发生异常时,传播方向取决于异常类型:

        这种传播机制本质是责任链模式的体现:每个 Job 既是子 Job 的管理者(处理向下传播),又是父 Job 的责任节点(处理向上传播),确保异常能沿着树结构扩散至整个 Scope。

  • 若为CancellationException(主动取消):仅向下传播(取消当前 Job 的所有子 Job),父 Job 会忽略该异常(源码中childCancelled方法对CancellationException直接返回false,不触发父 Job 取消)。
  • 若为其他异常(如IOException):先向下取消所有子 Job,再向上传播至父 Job,父 Job 接收异常后会触发自身取消,并继续向上传播,最终导致根 Job(CoroutineScope的 Job)取消,所有关联协程终止。

阶级总结:

        CoroutineScope管理协程的核心在于其coroutineContext中必然存在的Job元素,该元素作为根节点,在协程创建时通过launchasync等构建器实例化AbstractCoroutine子类,这些子类在initParentJob()方法中会将自身持有的子Job与根Job或上级协程的父Job绑定,形成以根Job为顶点的 N 叉树结构;

        当取消事件或异常发生时,基于该树结构和JobSupport的事件通知机制,CancellationException仅向下传播取消当前Job的所有子Job且父Job会忽略该异常,而其他异常则先向下取消所有子Job,再通过责任链模式向上传播至父Job,最终可能导致根Job取消,从而实现对协程的结构化管理。

http://www.lryc.cn/news/583050.html

相关文章:

  • 中望CAD2026亮点速递(5):【相似查找】高效自动化识别定位
  • uniapp AndroidiOS 定位权限检查
  • Android ViewModel机制与底层原理详解
  • upload-labs靶场通关详解:第19关 条件竞争(二)
  • 池化思想-Mysql异步连接池
  • 5.注册中心横向对比:Nacos vs Eureka vs Consul —— 深度解析与科学选型指南
  • Web 前端框架选型:React、Vue 和 Angular 的对比与实践
  • 华为静态路由配置
  • 小米路由器3C刷OpenWrt,更换系统/变砖恢复 指南
  • 语音识别核心模型的数学原理和公式
  • 从互联网电脑迁移Dify到内网部署Dify方法记录
  • 【编程史】IDE 是谁发明的?从 punch cards 到 VS Code
  • 计算机网络实验——访问H3C网络设备
  • Java项目集成Log4j2全攻略
  • Using Spring for Apache Pulsar:Publishing and Consuming Partitioned Topics
  • 飞算 JavaAI 智能编程助手 - 重塑编程新模态
  • bash 判断 /opt/wslibs-cuda11.8 是否为软连接, 如果是,获取连接目的目录并自动创建
  • (C++)任务管理系统(正式版)(迭代器)(list列表基础教程)(STL基础知识)
  • `fatal: bad config value for ‘color.ui‘`错误解决方案
  • ali linux 安装libreoffice
  • Markdown入门
  • 类和对象拓展——日期类
  • Django核心知识点详解:JSON、AJAX、Cookie、Session与用户认证
  • npu-smi info 华为昇腾NPU 状态监控工具解读
  • 类与对象【下篇】-- 关于类的其它语法
  • 树莓派vsftpd文件传输服务器的配置方法
  • 【02】MFC入门到精通——MFC 手动添加创建新的对话框模板
  • overleaf 改为XeLatex
  • Vue响应式原理四:响应式-监听属性变化
  • 正点原子学习 用户权限管理