导图社区 kotlin协程
This is a mind map about 协程,Main content: 基础部分,协程返回对象,三大核心概览,三大核心,协程模式,协程取消,调度器,异常处理,三大支柱。
编辑于2025-02-02 22:03:47协程
三大支柱
结构化并发(Structured Concurrency)
核心思想
协程必须在一个 作用域(CoroutineScope) 内启动,作用域负责管理其生命周期。
规则
父作用域的生命周期 > 子协程:父作用域取消时,所有子协程自动取消
子协程异常 > 父作用域:子协程未捕获的异常会取消父作用域(默认行为)
挂起函数(Suspend Function)
行为
可以在不阻塞线程的情况下暂停执行,并在后台任务完成后恢复
标记
用 suspend 关键字声明。
协程上下文(CoroutineContext)
包含
Job:管理协程的生命周期(取消、父子关系)
Dispatcher:决定协程在哪个线程执行(如 Dispatchers.Main, Dispatchers.IO)
CoroutineName:协程的名称,调试的时候很有用
CoroutineExceptionHandler:处理未捕获的异常。
自定义协程上下文
val context = Dispatchers.IO + SupervisorJob() + exceptionHandler
fun test_coroutines() = runBlocking { val Context_1 = Dispatchers.Default+CoroutineName("Context_1") val work_1 = launch(context = Context_1){ println("Im working in Context_1 and thread name is:"+Thread.currentThread().name) } }
协程上下文的继承
默认继承:子协程会继承父协程的上下文。 显式覆盖:在子协程的构建器中指定新的上下文元素,会覆盖父协程的相同元素 组合方式:使用 + 操作符合并父协程和子协程的上下文,后者优先级更高。
val parentContext = Dispatchers.Main + CoroutineName("Parent") + Job() val childJob = launch(parentContext + Dispatchers.IO) { // 子协程上下文:Dispatchers.IO + CoroutineName("Parent") + 新的子Job }
三大核心
协程作用域(CoroutineScope)
生命周期管理
通过 scope.cancel() 取消所有子协程
上下文传递
为子协程提供默认的 CoroutineContext。
自定义协程作用域
//定义一个协程上下文 val context = Dispatchers.IO + SupervisorJob()+exceptionHandler //自定义作用域 val customScope = CoroutineScope( context ) fun main4() = runBlocking { customScope.launch { withContext(Dispatchers.Main) {} } } // 取消作用域(如组件销毁时) fun onDestroy() { customScope.cancel()}
协程作用域构建器
coroutineScope
在挂起函数内部创建一个新的作用域,继承父协程的上下文,并等待所有子协程完成。
核心特性: 结构化并发:任一子协程失败会取消整个作用域。 自动上下文继承:子协程默认使用父协程的调度器、Job 等。
suspend fun fetchData() = coroutineScope { val user = async { fetchUser() } val posts = async { fetchPosts() } Data(user.await(), posts.await()) }
supervisorScope
创建一个新的作用域,使用 SupervisorJob,允许子协程独立失败(一个子协程失败不影响其他子协程)。
核心特性: 子协程异常隔离:子协程失败不会取消父作用域或其他子协程。 手动处理异常:需在子协程内部捕获异常。
suspend fun loadData() = supervisorScope { launch { try { fetchData1() } catch (e: Exception) { /* 处理异常 */ } } launch { try { fetchData2() } catch (e: Exception) { /* 处理异常 */ } } }
runBlocking
在常规阻塞代码中启动协程,阻塞当前线程,直到内部协程全部完成。
核心特性: 线程阻塞:主要用于桥接协程与阻塞代码(如 main 函数、单元测试)。 慎用场景:在 Android 主线程中使用会导致 ANR。
fun main() = runBlocking { launch { delay(1000); println("Hello") } println("World") // 输出顺序:World → Hello }
withContext(特殊场景)
临时切换协程的上下文(如线程池),并返回结果。虽然主要用途是切换调度器,但隐式创建一个子作
核心特性: 不阻塞父协程:仅挂起当前协程,执行完成后恢复父协程的上下文。
suspend fun loadData(): String = withContext(Dispatchers.IO) { // 在 IO 线程执行阻塞操作 blockingNetworkCall() }
协程构建器
launch
用途:启动一个 不返回结果 的协程。
lifecycleScope.launch { val data = fetchData() // 挂起函数 updateUI(data) }
async
用途:启动一个 返回 Deferred 结果 的协程,通过 await() 获取结果。
lifecycleScope.launch { val dataDeferred = async { fetchData() } val result = dataDeferred.await() // 等待结果 }
val one = async { doOne() } val two = async { doTwo() } println("The result:${one.await() + two.await()}") //运行结果是高并发的为1秒 //val one = async { doOne() }.await() //val two = async { doTwo() }.await() //println("The result:${one+ two}") //对比两种写法,下面这种写法等待的位置不对运行2秒 } println("Completed in $time ms") } private suspend fun doOne(): Int { delay(1000) return 14 } private suspend fun doTwo(): Int { delay(1000) return 25
结构并发的特点
协程取消
手动取消
作用域取消:通过作用域的 cancel() 方法取消所有子协程。
val scope = CoroutineScope(Dispatchers.IO) scope.cancel() // 取消作用域内所有协程
单个协程取消:通过 Job 对象取消特定协程。
val job = scope.launch { /* ... */ } job.cancel() // 取消单个协程
自动取消
父协程取消:父协程取消时,所有子协程会自动取消(结构化并发)。
coroutineScope { launch { /* 子协程1 */ } launch { /* 子协程2 */ } throw CancellationException() // 取消父协程,所有子协程被取消 }
超时取消:通过 withTimeout 或 withTimeoutOrNull 设置超时时间。
val result = withTimeout(3000) { // 3秒超时 fetchData() // 超时后协程被取消,抛出 TimeoutCancellationException }
CPU密集型任务取消
isActive
isActive 是 CoroutineScope 的布尔属性,表示当前协程是否仍处于活跃状态(未被取消)。 手动检查:在 CPU 密集型循环中,通过 isActive 手动检测协程是否被取消,决定是否退出任务。
scope.launch { var i = 0 while (isActive) { // 每次循环检查是否已取消 computeNextValue(i++) // CPU 密集型计算 } cleanupResources() // 清理资源 }
ensureActive()
ensureActive() 是 CoroutineScope 的扩展函数,若协程已取消,立即抛出 CancellationException。 快速失败:代替手动检查 isActive,简化取消逻辑。
scope.launch { var i = 0 while (true) { ensureActive() // 若取消,直接抛出异常 computeNextValue(i++) } }
yield()
yield() 是挂起函数,主动让出当前线程的执行权,允许其他协程运行。隐式取消检查:在让出线程时,自动检查协程是否已取消,若已取消则抛出 CancellationException。
scope.launch { var i = 0 while (true) { yield() // 让出线程,并检查取消状态 computeNextValue(i++) } }
finally
协程的取消会触发 CancellationException,导致协程中断。如果协程持有资源(如文件句柄、数据库连接、网络请求等),必须在取消时正确释放,否则可能导致资源泄漏,进而引发性能问题或应用崩溃。
val job = launch { val resource = acquireResource() // 获取资源(如打开文件) try { useResource(resource) // 使用资源(可能包含挂起函数) } finally { releaseResource(resource) // 确保释放资源 } }
withTimeout
withTimeout 是 Kotlin 协程提供的一个 超时控制函数,用于在指定时间内执行协程代码块。若超时未完成,则抛出 TimeoutCancellationException 并取消协程。
异常处理
抛出异常的方式
try-catch
lifecycleScope.launch { try { fetchData() } catch (e: Exception) {showError(e)}}
全局
val exceptionHandler = CoroutineExceptionHandler { _, e -> Log.e("Coroutine", "全局异常: $e") } val scope = CoroutineScope(Dispatchers.IO + exceptionHandler) scope.launch { throw RuntimeException("Oops!") }
CoroutineExceptionHandler
//自定义协程并绑定异常 val handler = CoroutineExceptionHandler { _, e -> println("全局捕获异常: ${e.message}") } // 将 Handler 绑定到作用域的上下文中 val scope = CoroutineScope(Job() + handler) // 根协程:直接通过 scope.launch 启动 scope.launch { throw IllegalArgumentException("测试异常") // 触发 Handler }
//在根协程的 launch 参数中设置 val handler = CoroutineExceptionHandler { _, e -> println("捕获异常: ${e.message}") } // 根协程中直接指定 Handler CoroutineScope(Job()).launch(handler) { throw RuntimeException("根协程异常") // 触发 Handler }
// 根协程(GlobalScope) GlobalScope.launch { throw RuntimeException("launch 异常") // 导致应用崩溃(未捕获) } val deferred = GlobalScope.async { throw RuntimeException("async 异常") // 不会立即崩溃 } deferred.await() // 此时触发异常
根协程的异常处理:根协程是协程树的顶层协程,没有父协程(或使用 SupervisorJob 隔离父子关系)
launch 启动的根协程
默认行为:未捕获的异常会直接抛出,导致协程崩溃。 处理方式:通过 CoroutineExceptionHandler 捕获异常。
val exceptionHandler = CoroutineExceptionHandler { _, throwable -> println("Root coroutine exception: $throwable") } val scope = CoroutineScope(Job() + exceptionHandler) scope.launch { throw RuntimeException("Root launch failed!") }
async 启动的根协程
默认行为:异常不会立即抛出,而是在调用 await() 时抛出。 处理方式:用 try-catch 包裹 await(),或结合 CoroutineExceptionHandle
val deferred = scope.async { throw RuntimeException("Root async failed!") } try { deferred.await() } catch (e: Exception) { println("Caught async exception: $e") }
非根协程(子协程)的异常处理:子协程由父协程启动,遵循结构化并发规则,异常默认会传播到父协程
默认行为:异常自动传播:子协程抛出异常时,会取消父协程及其所有子协程。
val parentJob = launch { // 子协程 launch { throw RuntimeException("Child failed!") } // 父协程也会被取消 }
使用 SupervisorJob 或 supervisorScope:隔离异常传播,仅取消抛出异常的协程。
// 方式1:使用 SupervisorJob val scope = CoroutineScope(SupervisorJob()) // 方式2:在子协程内部使用 supervisorScope launch { supervisorScope { launch { throw RuntimeException("Child failed!") } launch { println("Other child still works!") } } }
协程模式
调度器
协程返回对象
JOB
job状态流程图
协程创建(New) ↓ start() → 活跃(Active) ↓ 代码执行中 → 完成中(Completing) → 已完成(Completed) ↓ ↓ cancel() → 取消中(Cancelling) → 已取消(Cancelled)
job方法
在 Kotlin 协程中,join() 是一个 挂起函数,它的作用是挂起当前协程(调用 join() 的协程),直到 目标协程(被调用的 Job 对应的协程) 执行完成。 目标协程:调用 join() 方法的 Job 对象所代表的协程。 当前协程:调用 join() 的协程(即执行 join() 的协程)。 2. join() 的行为 挂起当前协程:调用 job.join() 时,当前协程会被挂起,直到 job 对应的协程完成(无论成功、失败或取消)。不阻塞线程:虽然协程被挂起,但底层线程不会被阻塞,可以继续执行其他任务。
//invokeOnCompletion val job = launch { // 协程代码 } job.invokeOnCompletion { cause: Throwable? -> if (cause is CancellationException) { println("协程被取消") } else { println("协程正常完成") } }
Job 状态检查属性
supervisorjob
Deferred(继承自 Job)
三大核心概览
协程作用域
协程构建器
协程作用域构造器
基础部分
依赖
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.x.x" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.x.x" // 如果在 Android 项目中使用
核心思想
协程必须在一个 作用域(CoroutineScope) 内启动,作用域负责管理其生命周期。
import kotlinx.coroutines.* import org.junit.Test class SupervisorJobTest { @Test fun `test SupervisorJob`() = runBlocking<Unit> { // 创建使用 SupervisorJob 的作用域 val supervisorScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) // 子协程 Job1:立即抛出异常 val job1 = supervisorScope.launch { try { delay(100) // 模拟短暂延迟 println("Child 1: 正常执行完毕") } catch (e: Exception) { println("Child 1: 捕获到异常 ${e.javaClass.simpleName}") } throw IllegalArgumentException("Job1 异常") // 故意抛出异常 } // 子协程 Job2:独立任务,尝试在 finally 中执行清理 val job2 = supervisorScope.launch { try { println("Child 2: 开始执行") delay(200) // 模拟耗时操作 } finally { println("Child 2: 进入 finally 块") delay(50) // 模拟清理操作(短暂延迟) println("Child 2: 完成清理") } } // 等待两个子协程完成(或失败) joinAll(job1, job2) supervisorScope.cancel() // 显式取消作用域(模拟组件销毁) } }
job1 抛出异常后,job2 仍会继续执行至完成。 Child 2: 开始执行 Child 1: 捕获到异常 CancellationException Child 2: 进入 finally 块 Child 2: 完成清理 SupervisorJob 使子协程的失败不会传播给父级或其他子协程。 job1 的异常仅导致自身取消,job2 不受影响。
import kotlinx.coroutines.* fun main() = runBlocking { // 示例 1: DEFAULT 模式 val job1 = launch(start = CoroutineStart.DEFAULT) { println("DEFAULT 模式执行") // 若在调度前取消,不会打印 } job1.cancel() // 示例 2: ATOMIC 模式 val job2 = launch(start = CoroutineStart.ATOMIC) { println("ATOMIC 模式初始化") // 即使被取消,仍会执行 delay(100) println("ATOMIC 模式后续操作") // 取消后不执行 } job2.cancel() // 示例 3: LAZY 模式 val job3 = async(start = CoroutineStart.LAZY) { println("LAZY 模式执行") // 显式调用 job3.await() 才会执行 } job3.cancel() job3.await() // 抛出 JobCancellationException // 示例 4: UNDISPATCHED 模式 launch(Dispatchers.IO, CoroutineStart.UNDISPATCHED) { println("UNDISPATCHED 模式当前线程: ${Thread.currentThread().name}") // main delay(100) println("UNDISPATCHED 模式切换后线程: ${Thread.currentThread().name}") // DefaultDispatcher-worker-1 } }
1. 协程创建与调度 协程创建:launch(start = CoroutineStart.DEFAULT) 创建协程,并指定启动模式为 DEFAULT。 立即调度:在 DEFAULT 模式下,协程会 立即提交到调度器队列(如 Dispatchers.Default 的线程池),但具体执行时间由调度器决定(异步行为)。 2. 主线程执行 job1.cancel() 取消操作:主线程在协程创建后立即调用 job1.cancel()。 情况 1:调度前取消 如果协程尚未被调度器从队列中取出执行,取消操作会将协程标记为“已取消”。此时协程代码块(println)不会执行。 情况 2:调度后取消 如果协程已经被调度器取出并开始执行,取消操作会触发协程的取消逻辑。协程会在下一个挂起点(如 delay())或主动检查取消状态时终止。 ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ 主线程执行流程: 调度器线程池: 1. 创建协程 job1 2. 将 job1 提交到调度器队列 3. 调用 job1.cancel() → 若取消发生在调度前: - job1 未执行,直接取消。 → 若取消发生在调度后: - job1 已开始执行,检测到取消后终止。