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 声明式处理,可以获取到异常信息

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值