Kotlin协程

Kotlin 协程

Coroutines(协程)是一种编程思想,并不局限于特定的语言。

协程可以使用阻塞的方式写出非阻塞式的代码,解决并发中常见的回调地狱。消除了并发任务之间的协作的难度。


在 android 中使用
引入依赖

root build.gradle

plugins {
	// kotlin编译插件
    id 'org.jetbrains.kotlin.android' version '1.5.30' apply false
}

app build.gradle

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1")// 协程核心库
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1")// 协程android支持库

创建协程
  • 方式一:使用 runBlocking 顶层函数,线程阻塞,一般用于单元测试
  • 方式二:使用 GlobalScope 单例对象,和应用生命周期一致,一般不推荐使用
  • 方式三:通过 CoroutineContext(协程上下文) 创建一个 CoroutineScope(协程作用域) 对象

// 使用顶层函数 runBlocking 启动协程
runBlocking {
    // 协程的内容
}

// GlobalScope 启动协程
GlobalScope.launch { 
   // 协程的内容
}

// 通过 协程上下文创建协程作用域,然后启动协程
val coroutineScope = CoroutineScope(Job() + Dispatchers.Main)
coroutineScope.launch { 
  // 协程的内容
}

CoroutineContext(协程上下文)

表示协程的运行环境,是一组定义协程行为的元素,它由以下几项组成:

  • Job:协程任务,可以取消,用于控制协程的生命周期
  • Dispatcher:协程调度器,用于切换协程中当前任务执行的线程
  • CoroutineName:协程名称,可以自定义协程的名称,用于调试日志输出
  • CoroutineExceptionHandler:协程异常处理器,处理未被捕获的异常

创建 CoroutineScope

有时我们需要在协程上下文中定义多个元素,组合协程上下文中的元素,使用 + 操作符来创建 CoroutineScope .

CoroutineScope(Job() + Dispatchers.Main + CoroutineName("my_coroutine_1"))
CoroutineScope(SupervisorJob() + Dispatchers.IO + CoroutineName("my_coroutine_2"))

Job(协程任务)

Joblaunch 构建协程返回的一个协程任务,Job 具有生命周期并且可以取消。

  • isActive:当前协程是否处于活跃状态。
  • isCompleted:当前协程任务是否已经结束,已取消、已失败和已完成都被视为已经结束。
  • isCancelled:当前协程任务是否处于已取消状态。

CoroutineScope(协程作用域)

定义协程必须指定其 CoroutineScope,它会跟踪所有协程,同样它也可以取消由它启动的协程。

  • GlobalScope:生命周期是应用级别的。
  • MainScope:一个顶层函数,上下文是 SupervisorJob() + Dispatchers.Main,说明它是一个在主线程执行的协程作用域,通过 cancel 对协程进行取消。
  • viewModelScope: 只能在 ViewModel 中使用,绑定 ViewModel 的生命周期。
  • lifecycleScope: 只能在 Activity、Fragment 中使用,会绑定 Activity、Fragment 生命周期。

CoroutineStart(协程启动模式)
  • DEFAULT:默认的启动模式,协程创建后,立即开始调度,在调度前如果协程被取消,其将直接进入取消响应的状态。
  • ATOMIC:协程创建后,立即开始调度,协程执行到第一个挂起点之前不响应取消。
  • LAZY:只有协程被需要时,包括主动调用协程的 start、join 或 await 等函数时才会开始调度,如果调度前就被取消,那么该协程将直接进入异常结束状态。
  • UNDISPATCHED:协程创建后立即在当前函数调用栈中执行,直到遇到第一个真正挂起的点。

Dispatchers(协程调度器)
  • Default:默认调度器,用于处理 CPU 密集型任务,后台线程
  • Main:主线程调度器,用于刷新 UI
  • Unconfigned:不局限于任何特定线程的协程调度器
  • IO:IO调度器,用于 IO 相关的操作处理,后台线程

launch 和 async

CoroutineScope 的顶层函数,都是用来启动一个协程。

  • launch:返回一个 Job 并且不附带任何结果值。
  • async:返回一个 Deferred,Deferred 也是一个 Job,可以使用 await() 函数获得该协程返回的结果。常用于并发执行,同步等待和获取返回值的情况。
runBlocking {
    val job1 = launch {
		// 协程的内容
    }

    val job2 = async {
		 //协程的内容
        "SUCCESS"// 最后一行 为该协程返回的结果
    }
    val result = job2.await()// 调用 await 会挂起,等待协程任务的返回
}

协程的作用域构建器
coroutineScope / supervisorScope

都是一个挂起函数,需要在协程中或挂起函数中创建。用于构建一个独立的协程作用域,等待其协程体以及所有子协程结束。

  • coroutineScope:作用域中的任何一个协程失败了,其他协程也会被取消。
  • supervisorScope:作用域中的子协程的异常不会影响到其他协程。

协程的取消
  • 取消 CoroutineScope (协程作用域),会取消它的子协程。
  • 取消 CoroutineScope (协程作用域)中的子协程,并不会影响其余兄弟协程。
  • 协程通过抛出一个特殊的异常 CancellationException 来处理取消操作。
  • 所有的挂起函数(withContext、delay 等)都是可取消的。

协程取消的副作用

可能会造成申请的资源没有释放,解决方法:

  • 使用 try…finally 释放资源。
  • 使用 use 函数,该函数只能被实现了 Closeble 的对象使用,程序结束后会自动调用 close 方法。

CPU 密集型任务取消

由于 CPU 密集型任务在 Job 取消的时候,不会被感知到,可能通过以下方式判断当前任务是否被取消:

  • isActive 是一个可以被使用在 CoroutineScope 中的扩展属性,检查 Job 是否处于活跃状态。
  • ensureActive(),如果 Job 处于非活跃状态,这个方法会立即抛出 CancellationException 予以响应 。
  • yield() ,如果 Job 处于非活跃状态,这个方法会立即抛出 CancellationException 予以响应 。此外它还会让出线程的执行权,给其他协程执行机会。

协程上下文的继承

对于新创建的协程,它的 CoroutineContext 会包含一个全新的 Job 实例。剩下的元素会从 CoroutineContext 的父类继承,该父类可能是另外一个协程或创建该协程的 CoroutineScope .


Job / SupervisorJob
  • Job:一个子协程的失败会影响其他协程。

  • SupervisorJob:一个子协程的失败不会影响其他子协程,不会传播异常给它的父级,它会让子协程自己处理异常。


异常的捕获
  • 使用 CoroutineExceptionHandler 对协程异常进行捕获

  • 以下的条件被满足时,异常就会被捕获

    • 时机:异常是被自动抛出异常的协程所抛出的(使用 launch,而不是 async 时);
    • 位置:在 CoroutineScope 的 CoroutineContext 中 或 一个根协程(CoroutineScope 或 supervisorScope 的直接协程)中

异常的传播特性

当一个协程由于一个异常而运行失败,它会传播这个异常并传递给它的父级,接下来父级会进行下面的步骤:

  • 取消它自己的子级
  • 取消它自己
  • 将异常传播并传递给他的父级

取消与异常
  • 取消与协程紧密相关,协程内部抛出 CancellationException 来进行取消,这个异常会被忽略。
  • 当子协程被取消时,不会取消它的父协程。
  • 如果一个协程遇到了 CancellationException 以外的异常,它将使用异常取消它的父协程。当父协程的所有子协程都结束后,异常才会被父协程处理。

withContext

用于切换到指定的线程,并在闭包内的逻辑执行结束后,会自动恢复到线程切换之前继续执行。

coroutineScope.launch(Dispatchers.Main) {// UI线程启动
    val image = withContext(Dispatchers.IO) {// 切换到IO线程
        getImage(imageId)
    }
    avatarIv.setImageBitamp(image)// 回到UI线程更新
}

suspend

suspend 表明当前函数是一个挂起函数,需要在协程或其他挂起函数中执行。代码执行到 suspend 函数时会挂起,这个挂起是非阻塞式的,它不会阻塞当前的线程。当挂起函数执行完毕后,会恢复到之前的协程中继续执行。


Flow

异步数据流。是一种类似于序列的冷流,flow 构建器中的代码直到流被收集的时候才运行。


Flow 与其他方式的区别
  • 名为 flowFlow 类型构建器函数
  • flow{…} 构建块中的代码可以挂起
  • 函数不再标有 suspend 修饰符
  • 流使用 emit 函数发射值
  • 流使用 collect 函数收集值

流构建器
  • flowOf 构建器定义了一个发射固定值集的流
  • 使用 .asFlow() 扩展函数,可以将各种集合与序列转换为流

流上下文
  • 流的收集总是在调用协程的上下文中发生,流的该属性称为 上下文保存。

  • flow{…} 构建器中的代码必须遵循上下文保存属性,并且不允许从其他上下文发射(emit)。

  • flowOn 操作符,该函数用于更改流发射的上下文。


启动流

使用 launchIn 替换 collect 可以在单独的协程中启动流的收集。


取消流

流采用与协程同样的协作取消。像往常一样,流的收集可以是当流在一个可取消的挂起函数(例如 delay)中挂起的时候取消。


流的取消检测
  • 流构建器对每个发射值执行附加的 ensureActive 检测以进行取消,这意味从 flow{…} 发出的繁忙循环是可以取消的。
  • 出于性能原因,大多数其他流操作不会自行执行其他取消检测,在协程处于繁忙循环的情况下,必须明确检测是否取消。
  • 通过 cancellable 操作来执行此操作。

被压

生产者的生产效率大于消费者的消费效率

  • buffer(),并发运行流中发射元素的代码。
  • conflate(),合并发射项,不对每个值进行处理。
  • collectLatest(),取消并重新发射最后一个值。
  • 当必须更改 CoroutineDispatcher 时,flowOn 操作符使用了相同的缓冲机制,但是 buffer 函数显式地请求缓冲而不改变执行上下文。

末端操作符

末端操作符是在流上用于启动流收集的挂起函数,collect 是最基础的末端操作符,但是还有另外一些更方便使用的末端操作符:

  • 转化为各种集合,例如 toList 与 toSet
  • 获取第一个(first)值与确保流发射单个(single)值的操作符
  • 使用 reduce 与 fold 将流规约到单个值

组合多个流

就像 Kotlin 标准库中的 Sequence.zip 扩展函数一样,流拥有一个 zip 操作符用于组合两个流中的相关值。


展平流

流表示异步接收的值序列,所以很容易遇到这样的情况:每个值都会触发对另一个值序列的请求,然而,由于流具有异步的性质,因此需要不同的展平模式。

  • flatMapConcat:连接模式
  • flatMapMerge:合并模式
  • flatMapLastest:最新展平模式

流的异常处理

当运算符中的发射器或代码抛出异常时

  • try/catch 块
  • catch 函数,处理上游异常

流的完成

当流收集完成时(普通情况或异常情况)它可能需要执行一个动作

  • 命令式 finally 块
  • onCompletion 声明式处理,可以获取到异常信息

<think>好的,我现在需要帮助用户解答关于Kotlin协程的使用教程、原理和示例的问题。用户还提到了一些引用内容,我需要结合这些引用来构建回答。 首先,我得理清楚Kotlin协程的基本概念。协程是轻量级的线程,用于简化异步编程。用户提供的引用[4]中提到,协程通过挂起函数避免了回调地狱,这应该作为原理部分的一个重点。 接下来,用户需要教程和示例。我应该分步骤讲解协程的使用,比如如何创建协程,常用的构建器如launch和async,以及异常处理。引用[1]和[3]提到了异常处理的方法,特别是CoroutineExceptionHandler的使用,但要注意它只在顶层协程有效。此外,引用[2]提到的Continuation可能涉及协程的底层原理,但用户可能更需要实际使用示例,所以这部分可能放在原理部分简要说明。 然后,我需要确保回答的结构清晰,分点讲解。可能的结构是:协程基础、使用教程(包括启动方式、作用域、调度器)、原理(挂起机制、恢复)、异常处理示例、应用场景。每个部分结合引用内容,比如在异常处理时引用[1]和[3],在异步编程部分引用[4]。 还要注意用户提到的系统级指令,比如数学公式的格式,但这个问题可能不涉及数学公式,所以可能不需要处理。但需要确保LaTeX语法正确,不过这里可能用不上。另外,生成相关问题,可能需要列出几个相关问题,如协程与线程的区别、如何选择调度器、异常处理的最佳实践等。 需要检查引用是否正确对应。引用[1]关于catch方法和onCompletion,引用[3]关于CoroutineExceptionHandler的使用限制,这些在异常处理部分需要提到。引用[2]涉及协程的创建和Continuation,可能在原理部分解释协程的挂起和恢复时提及。 最后,确保回答使用中文,代码示例正确,比如展示launch和async的使用,以及try-catch在协程中的示例。同时,问题部分要生成三个相关问题,帮助用户进一步学习。</think>### Kotlin 协程使用教程与原理详解 #### 一、协程基础 Kotlin 协程是轻量级的并发编程工具,通过**挂起函数**(Suspend Function)实现非阻塞异步操作。其核心目标是简化异步代码,避免回调地狱[^4]。 #### 二、使用教程 1. **协程启动方式** - `launch`:启动一个不返回结果的协程 - `async`:启动一个可返回结果的协程(需通过 `await()` 获取结果) ```kotlin // 示例:使用 launch 和 async val job = CoroutineScope(Dispatchers.IO).launch { val data = async { fetchData() } processData(data.await()) } ``` 2. **协程作用域** 使用 `CoroutineScope` 管理协程生命周期,避免内存泄漏: ```kotlin class MyActivity : CoroutineScope by CoroutineScope(Dispatchers.Main) { fun loadData() { launch { // 异步操作 } } override fun onDestroy() { cancel() // 取消所有子协程 } } ``` 3. **调度器(Dispatchers)** - `Dispatchers.Main`:Android 主线程 - `Dispatchers.IO`:文件/网络操作 - `Dispatchers.Default`:CPU密集型计算 #### 三、协程原理 1. **挂起与恢复机制** 协程通过 `suspend` 关键字标记可挂起函数。当遇到耗时操作时,协程挂起并释放线程资源,待操作完成后恢复执行[^4]。底层通过 `Continuation` 对象实现状态保存与恢复[^2]。 2. **协程与线程对比** | 特性 | 协程 | 线程 | |--------------|---------------------|---------------| | 资源占用 | 轻量级(KB级内存) | 重量级(MB级)| | 切换开销 | 无需系统调度 | 需要内核介入 | #### 四、异常处理 1. **try-catch 方式** ```kotlin launch { try { fetchData() } catch (e: Exception) { // 捕获异常 } } ``` > 注意:Kotlin 不建议直接在 Flow 中使用 `try-catch-finally`[^1] 2. **CoroutineExceptionHandler** ```kotlin val handler = CoroutineExceptionHandler { _, exception -> println("Caught $exception") } val scope = CoroutineScope(Job() + handler) scope.launch { throw RuntimeException("Error!") } ``` > 注意:仅对**顶层协程**有效[^3] #### 五、应用场景 1. 网络请求与数据库操作 2. 批量文件处理 3. 复杂状态机逻辑(如游戏引擎)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值