kotlin -协程学习的第六天

134 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

启动流

使用流表示来自一些源的异步事件是很简单的。 在这个案例中,我们需要一个类似 addEventListener 的函数,该函数注册一段响应的代码处理即将到来的事件,并继续进行进一步的处理。onEach操作符可以担任该角色。 然而,onEach 是一个过渡操作符。我们也需要一个末端操作符来收集流。 否则仅调用 onEach 是无效的。

如果我们在 onEach 之后使用 collect 末端操作符,那么后面的代码会一直等待直至流被收集:

    // 模仿事件流
    fun events(): Flow<Int> = (1..3).asFlow().onEach { delay(100) }

    fun main() = runBlocking<Unit> {
        events()
            .onEach { event -> println("Event: $event") }
            .collect() // <--- 等待流收集
        println("Done")
    } 

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

    fun main() = runBlocking<Unit> {
        events()
            .onEach { event -> println("Event: $event") }
            .launchIn(this) // <--- 在单独的协程中执行流
        println("Done")
    }

launchIn 必要的参数 CoroutineScope指定了用哪一个协程来启动流的收集

在实际的应用中,作用域来自于一个寿命有限的实体。在该实体的寿命终止后,相应的作用域就会被取消,即取消相应流的收集。这种成对的 onEach { ... }.launchIn(scope) 工作方式就像 addEventListener 一样。而且,这不需要相应的 removeEventListener 函数, 因为取消与结构化并发可以达成这个目的。

launchIn 也会返回一个 Job,可以在不取消整个作用域的情况下仅取消相应的流收集或对其进行 join

流的取消

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

fun simpleFlow3() = flow<Int>{
    for (i in 1..3){
        delay(1000)
        println("emitting $i")
        emit(i)
    }
}
fun  timeOutFun() = runBlocking{
    //流在超时的情况下取消并停止执行
    withTimeoutOrNull(2500){
        simpleFlow3().collect { 
            value -> println(value)
        }
    }
}

流的取消检测

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

背压

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

操作符

过渡流操作符
  • 可以使用操作符转换流,就像使用集合与序列一样
  • 过渡操作符应用于上游流,并返回下游流
  • 这些操作符也是冷操作符,就像流一样。这类操作符本身不是挂起函数
  • 它运行的速度很快,返回新的转换流的定义
末端流操作符

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

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

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

展平流

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

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

image.png

流的异常处理

当运算符中的发射器或者代码抛出异常时,有几种处理异常的方法

  • try/catch块
  • catch函数

流的完成

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

  • 命令式finally块
  • onCompletion声明式处理