kotlin -协程学习的第五天

230 阅读3分钟

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

Flow 异步流

挂起函数可以异步的返回单个值,但是该如何异步返回多个计算好的值呢?这正是 Kotlin 流(Flow)的用武之地。

异步返回多个值的方案
  • 集合
    fun simple(): List<Int> = listOf(1, 2, 3)
 
    fun main() {
        simple().forEach { value -> println(value) } 
    }
  • 序列

    如果使用一些消耗 CPU 资源的阻塞代码计算数字(每次计算需要 100 毫秒)那么我们可以使用 Sequence 来表示数字

    fun simple(): Sequence<Int> = sequence { // 序列构建器
        for (i in 1..3) {
            Thread.sleep(100) // 假装我们正在计算
            yield(i) // 产生下一个值
        }
    }

    fun main() {
        simple().forEach { value -> println(value) } 
    }
  • 挂起函数

    当这些值由异步代码计算时,我们可以使用 suspend 修饰符标记函数 simple, 这样它就可以在不阻塞的情况下执行其工作并将结果作为列表返回

    suspend fun simple(): List<Int> {
        delay(1000) // 假装我们在这里做了一些异步的事情
        return listOf(1, 2, 3)
    }

    fun main() = runBlocking<Unit> {
        simple().forEach { value -> println(value) } 
    }
  • Flow

    使用 List 结果类型,意味着我们只能一次返回所有值。 为了表示异步计算的值流(stream),我们可以使用 Flow 类型(正如同步计算值会使用 Sequence 类型)

    fun simple(): Flow<Int> = flow { // 流构建器
        for (i in 1..3) {
            delay(100) // 假装我们在这里做了一些有用的事情
            emit(i) // 发送下一个值
        }
    }
    fun main() = runBlocking<Unit> {
        // 启动并发的协程以验证主线程并未阻塞
        launch {
            for (k in 1..3) {
                println("I'm not blocked $k")
                delay(100)
            }
        }
        // 收集这个流
        simple().collect { value -> println(value) } 
    }

Flow与其他方式的区别

  • 名为 flow 的 Flow 类型构建器函数。

  • flow { ... } 构建块中的代码可以挂起。

  • 函数 simple 不再标有 suspend 修饰符。

  • 流使用 emit函数 发射值。

  • 流使用 collect 函数收集值。

image.png

Flow 应用

Android当中,文件下载就是Flow的一个非常典型的应用

image.png

冷流

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

   fun simple(): Flow<Int> = flow { 
        println("Flow started")
        for (i in 1..3) {
            delay(100)
            emit(i)
        }
    }

    fun main() = runBlocking<Unit> {
        println("Calling simple function...")
        val flow = simple()
        println("Calling collect...")
        flow.collect { value -> println(value) } 
        println("Calling collect again...")
        flow.collect { value -> println(value) } 
    }

流的连续性

  • 流的每次单独收集都是按顺序执行的,除非使用特殊操作符
  • 从上游到下游每个过渡操作符都会处理每个发射出的值,然后交给末端操作符
        (1..3).asFlow() // 一个请求流
        .map { request -> performRequest(request) }
        .collect { response -> println(response) }

流构建器

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

流上下文

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

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

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