Kotlin协程:Flow 异步流

异步流

1、Flow 介绍

Flow是kotlin提供的一个工具,使用协程封装成生产者-消费者模式,上流来负责生产,下流来接收消耗。

emit生产,collect消费
Flow

2、Flow 特性

  • flow{}构建块中的代码可以使用挂起函数
  • Flow构建器函数可以不用supend修饰符
  • 流的每次单独收集都是按顺序执行的,除非使用特殊操作符
  • Flow是一种类似序列的冷流,flow构建器中代码直到流被收集的时候才运行
//不需要用挂起函数修饰符
fun flowBuilder() = flow {
    for (i in 1..3) {
        delay(1000)//可以使用挂起函数
        emit(i)//发射元素
    }
}

//flowOf 不需要挂起函数修饰符 flowOf自动实现发射元素
fun flowOfBuilder() = flowOf(1, 2, 3, 4, 5)

//asFlow 不需要挂起函数修饰符 flowOf自动实现发射元素
fun asFlowBuilder() = (5..10).asFlow()

3、冷流、热流

Flow冷流:主动需要即是主动收集才会提供发射数据
Channel热流:不管你需不需要一上来数据全都发射给你

4、Flow构建器

①、flow{}

fun flowBuilder() = flow {
    for (i in 1..3) {
        delay(1000)//可以使用挂起函数
        emit(i)//发射元素
    }
}

@Test
fun flowStudy(): Unit = runBlocking {
 	//collect 需要在协程作用域中调用
    flowBuilder().collect(::println)
}

//执行输出
1
2
3

②、flowOf() 帮助可变数组生成 Flow 实例

fun flowOfBuilder() = flowOf(1, 2, 3, 4, 5)

@Test
fun flowStudy(): Unit = runBlocking {
	//collect 需要在协程作用域中调用
    flowOfBuilder().collect(::println)
}

//执行输出
1
2
3
4
5

③、asFlow 面向数组、列表等集合

fun asFlowBuilder() = (5..10).asFlow()

@Test
fun flowStudy(): Unit = runBlocking {
    asFlowBuilder().collect(::println)
}

//执行输出
5
6
7
8
9
10

5、Flow取消

①、withTimeoutOrNull

//不需要用挂起函数修饰符
fun flowBuilder() = flow {
    for (i in 1..3) {
        delay(1000)//可以使用挂起函数
        emit(i)//发射元素
        println("emit is send $i")
    }
}

@Test
fun flowStudy(): Unit = runBlocking {
    withTimeoutOrNull(2500){
        flowBuilder().collect(::println)
    }
    println("collect finish")
}

//执行输出
1
emit is send 1
2
emit is send 2
collect finish

②、cancel

流构建器对每个发射值执行附加的ensureActive检查以进行取消,这意味着从flow{…} 发出的繁忙循环是可以取消的。

fun flowCancelBuilder() = flow {
    for (i in 1..5) {
        delay(1000)
        emit(i)
        println("emit is send $i")
    }
}

@Test
fun flowCancelStudy(): Unit = runBlocking {
    flowCancelBuilder().collect { value -> if (value == 3) cancel() }
    println("collect finish")
}

//执行输出
emit is send 1
emit is send 2
emit is send 3

BlockingCoroutine was cancelled
kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@710636b0
	at app//kotlinx.coroutines.JobSupport.cancel(JobSupport.kt:1578)

③、cancellable

大多数其他流操作不会自行执行其他取消检查,在协程处于繁忙循环的情况下,必须通过 cancellable 明确检测是否取消

@Test
fun flowCancellableStudy(): Unit = runBlocking {
    (1..5).asFlow().collect { value ->
        println(value)
        if (value == 3) cancel()
    }
}

//执行输出
1
2
3
4
5

BlockingCoroutine was cancelled
kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@1e0b4072
	at app//kotlinx.coroutines.JobSupport.cancel(JobSupport.kt:1578)

上面这个示例这种一次性发射的繁忙循环并没有取消,还是收集完成了,要解决这个问题需要明确取消检查

使用 cancellable 就可以取消

@Test
fun flowCancellableStudy(): Unit = runBlocking {
    (1..5).asFlow().cancellable().collect { value ->
        println(value)
        if (value == 3) cancel()
    }
}

//执行输出
1
2
3

BlockingCoroutine was cancelled
kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@1a6d8329
	at app//kotlinx.coroutines.JobSupport.cancel(JobSupport.kt:1578)

6、Flow上下文

  • 流的收集总是在调用协程的上下文中发生,流的这个属性称为上下文保存
  • flow{}构建器中的代码必须遵循上下文保存属性,并且不允许从其他上下文中发射emit
  • 上游:flowOn函数,该函数用于更改流发射的协程
  • 下游:launchIn函数,改函数可以指定流收集处理协程,同时可以替换collect单独在协程中启动流的收集

①、上下文保存属性

示例:说明下游的上下文会传给上游上下文进行保存

fun flowContext() = flow {
    for (i in 1..3) {
        delay(1000)
        emit(i)
        println("emit send $i Thread=${Thread.currentThread().name}")
    }
}

@Test
fun flowContextStudy(): Unit = runBlocking {
    flowContext().collect{value ->
        println("value=$value Thread=${Thread.currentThread().name}")
    }
}

//执行输出
value=1 Thread=Test worker @coroutine#1
emit send 1 Thread=Test worker @coroutine#1
value=2 Thread=Test worker @coroutine#1
emit send 2 Thread=Test worker @coroutine#1
value=3 Thread=Test worker @coroutine#1
emit send 3 Thread=Test worker @coroutine#1

示例:如果将上游使用withContext调整协程,那么执行会出现异常,因为没有遵循上下文保存属性

fun flowContext() = flow {
    withContext(Dispatchers.IO) {
        for (i in 1..3) {
            delay(1000)
            emit(i)
            println("emit send $i Thread=${Thread.currentThread().name}")
        }
    }
}

@Test
fun flowContextStudy(): Unit = runBlocking {
    flowContext().collect { value ->
        println("value=$value Thread=${Thread.currentThread().name}")
    }
}

//执行输出
Flow invariant is violated:
		Flow was collected in [CoroutineId(1), "coroutine#1":BlockingCoroutine{Active}@5d113a91, BlockingEventLoop@7eb7a30],
		but emission happened in [CoroutineId(1), "coroutine#1":DispatchedCoroutine{Active}@3347186a, Dispatchers.IO].
		Please refer to 'flow' documentation or use 'flowOn' instead

②、flowOn函数

如果想切换上游的协程,需要使用flowOn 函数

fun flowContext() = flow {
    for (i in 1..3) {
        delay(1000)
        emit(i)
        println("emit send $i Thread=${Thread.currentThread().name}")
    }
}.flowOn(Dispatchers.IO)

@Test
fun flowContextStudy(): Unit = runBlocking {
    flowContext().collect { value ->
        println("value=$value Thread=${Thread.currentThread().name}")
    }
}

//执行结果
emit send 1 Thread=DefaultDispatcher-worker-1 @coroutine#2
value=1 Thread=Test worker @coroutine#1
emit send 2 Thread=DefaultDispatcher-worker-1 @coroutine#2
value=2 Thread=Test worker @coroutine#1
emit send 3 Thread=DefaultDispatcher-worker-1 @coroutine#2
value=3 Thread=Test worker @coroutine#1

③、launchIn函数

launchIn 是Flow的扩展函数,接收参数CoroutineScope,返回Job对象

public fun <T> Flow<T>.launchIn(scope: CoroutineScope): Job = scope.launch {
    collect() // tail-call
}
fun flowContext() = flow {
    for (i in 1..3) {
        delay(1000)
        emit(i)
        println("emit send $i Thread=${Thread.currentThread().name}")
    }
}

@Test
fun flowContextStudy(): Unit = runBlocking {
    flowContext().onEach { println("collect $it Thread=${Thread.currentThread().name}") }
        .launchIn(CoroutineScope(Dispatchers.Default)) //返回Job对象
        .join() //跟runBlocking  不是父子关系,所以要 join
}

//执行结果
collect 1 Thread=DefaultDispatcher-worker-2 @coroutine#2
emit send 1 Thread=DefaultDispatcher-worker-2 @coroutine#2
collect 2 Thread=DefaultDispatcher-worker-2 @coroutine#2
emit send 2 Thread=DefaultDispatcher-worker-2 @coroutine#2
collect 3 Thread=DefaultDispatcher-worker-2 @coroutine#2
emit send 3 Thread=DefaultDispatcher-worker-2 @coroutine#2

7、Flow背压

当发射效率大于收集效率时候会出现背压

fun flowPressure() = flow {
    for (i in 1..3) {
        delay(200)
        emit(i)
        println("send emit=$i")
    }
}

@Test
fun flowPressureStudy(): Unit = runBlocking {
    val time = measureTimeMillis {
        flowPressure().collect { value ->
            delay(400)
            println("collect value=$value")
        }
    }
    println("collect end time=$time")
}

//执行输出
collect value=1
send emit=1
collect value=2
send emit=2
collect value=3
send emit=3
collect end time=1898

如上示例背压需要 1898ms 如何进一步优化时长?

①、buffer

buffer相当于加长了管道,让收集有更多时间执行

使用buffer,将时长优化到1501 ms

fun flowPressure() = flow {
    for (i in 1..3) {
        delay(200)
        emit(i)
        println("send emit=$i")
    }
}

@Test
fun flowPressureStudy(): Unit = runBlocking {
    val time = measureTimeMillis {
        flowPressure().buffer(100).collect { value ->
            delay(400)
            println("collect value=$value")
        }
    }
    println("collect end time=$time")
}

//执行输出
send emit=1
send emit=2
collect value=1
send emit=3
collect value=2
collect value=3
collect end time=1501

②、flowOn

flowOn 改变发射段的线程,时间优化到1505 ms

fun flowPressure() = flow {
    for (i in 1..3) {
        delay(200)
        emit(i)
        println("send emit=$i")
    }
}.flowOn(Dispatchers.IO)

@Test
fun flowPressureStudy(): Unit = runBlocking {
    val time = measureTimeMillis {
        flowPressure().collect { value ->
            delay(400)
            println("collect value=$value")
        }
    }
    println("collect end time=$time")
}

//执行输出
send emit=1
send emit=2
collect value=1
send emit=3
collect value=2
collect value=3
collect end time=1504

③、conflate

合并发射项,不对每个值进行处理,就是放弃中间一些值的处理

示例:这个例子发射值太少,都收集到了,但是还是有优化效果 1500ms ,可以增加发射值来验证,收集的数据肯定会缺少

fun flowPressure() = flow {
    for (i in 1..3) {
        delay(200)
        emit(i)
        println("send emit=$i")
    }
}

@Test
fun flowPressureStudy(): Unit = runBlocking {
    val time = measureTimeMillis {
        flowPressure().conflate().collect { value ->
            delay(400)
            println("collect value=$value")
        }
    }
    println("collect end time=$time")
}

//执行输出
send emit=1
send emit=2
collect value=1
send emit=3
collect value=2
collect value=3
collect end time=1500

④、collectLatest

取消并重新发射最后一个值,就是只收集最后一个值

fun flowPressure() = flow {
    for (i in 1..3) {
        delay(200)
        emit(i)
        println("send emit=$i")
    }
}

@Test
fun flowPressureStudy(): Unit = runBlocking {
    val time = measureTimeMillis {
        flowPressure().collectLatest { value ->
            delay(400)
            println("collect value=$value")
        }
    }
    println("collect end time=$time")
}

//执行输出
send emit=1
send emit=2
send emit=3
collect value=3
collect end time=1174

8、Flow异常处理

①、发射端异常处理(上游)

发射端异常处理一般使用 catch { } 处理

fun flowException() = flow {
    for (i in 1..3) {
        delay(200)
        emit(i)
        println("send emit=$i")
        throw NoSuchMethodException("发射端异常")
    }
}.catch { println("捕捉到发射端异常") }

@Test
fun flowExceptionStudy(): Unit = runBlocking {
    val time = measureTimeMillis {
        flowException().collect { value ->
            delay(400)
            println("collect value=$value")
        }
    }
    println("collect end time=$time")
}

//执行输出
collect value=1
send emit=1
捕捉到发射端异常
collect end time=664

②、收集端异常处理(下游)

收集端异常一般使用 try{ } catch{ } 处理

fun flowException() = flow {
    for (i in 1..3) {
        delay(200)
        emit(i)
        println("send emit=$i")
    }
}

@Test
fun flowExceptionStudy(): Unit = runBlocking {
    val time = measureTimeMillis {
        try {
            flowException().collect { value ->
                delay(400)
                println("collect value=$value")
                throw NoSuchMethodException("收集端异常")
            }
        }catch (e:Exception){
            println("捕捉到收集端异常")
        }
    }
    println("collect end time=$time")
}

//执行输出
collect value=1
捕捉到收集端异常
collect end time=648

9、Flow的完成

①、命令式处理finally

fun flowCompletion() = flow {
    for (i in 1..3) {
        delay(200)
        emit(i)
        println("send emit=$i")
        throw NoSuchMethodException("收集端异常")
    }
}

@Test
fun flowCompletionStudy(): Unit = runBlocking {
    try {
        flowCompletion().collect { value ->
            delay(400)
            println("collect value=$value")
        }
    }finally {
        println("collect onCompletion")
    }
}

//执行输出
collect value=1
send emit=1
collect onCompletion

收集端异常
java.lang.NoSuchMethodException: 收集端异常
	at com.example.mycoroutine.Flow1$flowCompletion$1.invokeSuspend(Flow1.kt:87)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)

②、onCompletion声明式处理

onCompletion 只是会收集异常信息,但是不会捕捉异常

fun flowCompletion() = flow {
    for (i in 1..3) {
        delay(200)
        emit(i)
        println("send emit=$i")
        throw NoSuchMethodException("收集端异常")
    }
}

@Test
fun flowCompletionStudy(): Unit = runBlocking {
    flowCompletion().onCompletion { exp ->
        println("collect onCompletion exp=$exp")
    }.collect { value ->
        delay(400)
        println("collect value=$value")
    }
}

//执行输出
collect value=1
send emit=1
collect onCompletion exp=java.lang.NoSuchMethodException: 收集端异常

收集端异常
java.lang.NoSuchMethodException: 收集端异常
	at com.example.mycoroutine.Flow1$flowCompletion$1.invokeSuspend(Flow1.kt:87)

10、Flow操作符

①、transfrom 转换

可以任意多次调用emit

@Test
fun flowTransformStudy(): Unit = runBlocking {
    (1..3).asFlow().transform {
        emit(it * it)
        emit("add it$it")
    }.collect { value ->
        println("collect value=$value")
    }
}

//执行输出
collect value=1
collect value=add it1
collect value=4
collect value=add it2
collect value=9
collect value=add it3

②、take 限长

现在收集长度

@Test
fun flowTakeStudy(): Unit = runBlocking {
    (1..3).asFlow().take(2).collect{value ->
        println("collect value=$value")
    }
}

//执行输出
collect value=1
collect value=2

③、zip 组合

组合两个流数据

@Test
fun flowZipStudy(): Unit = runBlocking {
    val asFlow = (1..3).asFlow()
    val flowOf = flowOf("kotlin", "java", "c++")
    asFlow.zip(flowOf) { flowOne, flowTwo ->
        "$flowOne->$flowTwo"
    }.collect(::println)
}

//执行输出
1->kotlin
2->java
3->c++

④、flattenMerge

flattenMerge不会组合多个flow,而是将它们作为单个流执行。
flattenMerge和flattenConcat类似,但是可以设置并发收集的流的数量,如果设置成1,那么就等同于 flattenConcat

@Test
fun flowFlattenMergeStudy(): Unit = runBlocking {
    val flowA = (1..5).asFlow()
    val flowB = flowOf("one", "two", "three", "four", "five")

    flowOf(flowA, flowB).flattenMerge(2).collect {
        println("flattenMerge:$it")
    }
}
//执行输出
flattenMerge:1
flattenMerge:2
flattenMerge:3
flattenMerge:4
flattenMerge:5
flattenMerge:one
flattenMerge:two
flattenMerge:three
flattenMerge:four
flattenMerge:five

⑤、flattenConcat

@Test
fun flowFlattenConcatStudy(): Unit = runBlocking {
    val flowA = (1..5).asFlow()
    val flowB = flowOf("one", "two", "three", "four", "five")

    flowOf(flowA, flowB).flattenConcat().collect { println("flattenConcat:$it") }
}
//执行输出
flattenConcat:1
flattenConcat:2
flattenConcat:3
flattenConcat:4
flattenConcat:5
flattenConcat:one
flattenConcat:two
flattenConcat:three
flattenConcat:four
flattenConcat:five

⑥、flatMapConcat

在调用 flatMapConcat 后,collect 函数在收集新值之前会等待 flatMapConcat 内部的 flow 完成

fun currTime() = System.currentTimeMillis()
var start:Long=0

@Test
fun flowFlatMapConcatStudy(): Unit = runBlocking {

    (1..5).asFlow().onStart {
        start=currTime()
    }.flatMapConcat {
        flow {
            emit("$it kotlin")
            delay(100)
            emit("$it java")
        }
    }.collect {
        println("collect $it at ${System.currentTimeMillis()-start}")
    }
}

// 执行输出
collect 1 kotlin at 7
collect 1 java at 126
collect 2 kotlin at 126
collect 2 java at 236
collect 3 kotlin at 236
collect 3 java at 345
collect 4 kotlin at 346
collect 4 java at 454
collect 5 kotlin at 455
collect 5 java at 567

⑦、flatMapMerge

并发收集flows并且将合并它们的值为一个单一flow,因此发射地值会尽快被处理

fun currTime() = System.currentTimeMillis()
var start:Long=0

@Test
fun flowFlatMapMergeStudy(): Unit = runBlocking {

    (1..5).asFlow()
        .onStart {
            start = currTime()
        }
        .flatMapMerge {
            flow {
                emit("$it kotlin")
                delay(100)
                emit("$it java")
            }
        }.collect {
            println("collect $it at ${System.currentTimeMillis() - start}")
        }

}

// 执行输出 可以看出来flatMapMerge并发特性
collect 1 kotlin at 13
collect 2 kotlin at 14
collect 3 kotlin at 15
collect 4 kotlin at 15
collect 5 kotlin at 15
collect 1 java at 119
collect 2 java at 119
collect 3 java at 119
collect 4 java at 119
collect 5 java at 120

⑧、flatMapLatest

flatMapLatest和collectLatest操作符很像,只要新flow发射了新值,那么上个flow就会被取消。

fun currTime() = System.currentTimeMillis()
var start:Long=0

@Test
fun flowFlatMapLatestStudy(): Unit = runBlocking {

    (1..5).asFlow().onStart {
        start = currTime()
    }.flatMapLatest {
        flow {
            emit("$it kotlin")
            delay(100)
            emit("$it java")
        }
    }.collect {
        println("collect $it at ${System.currentTimeMillis() - start}")
    }

}

// 执行输出
collect 1 kotlin at 12
collect 2 kotlin at 41
collect 3 kotlin at 41
collect 4 kotlin at 42
collect 5 kotlin at 43
collect 5 java at 148

⑨、reduce

就是两个元素操作之后拿到的值跟后面的元素进行操作,用于把flow 简化合并为一个值

@Test
fun flowReduceStudy(): Unit = runBlocking {
    val reduceResult = (1..5).asFlow().reduce { valueA, valueB ->
        println("reduce valueA=$valueA,valueB=$valueB")
        valueA + valueB
    }

    println("reduceResult=$reduceResult")
}

//执行输出
reduce valueA=1,valueB=2
reduce valueA=3,valueB=3
reduce valueA=6,valueB=4
reduce valueA=10,valueB=5
reduceResult=15

⑩、fold

@Test
fun flowFoldStudy(): Unit = runBlocking {
    val reduceResult = (1..5).asFlow().fold(2) { valueA, valueB ->
        println("fold valueA=$valueA,valueB=$valueB")
        valueA * valueB
    }
    println("reduceResult=$reduceResult")
}

//执行输出
fold valueA=2,valueB=1
fold valueA=2,valueB=2
fold valueA=4,valueB=3
fold valueA=12,valueB=4
fold valueA=48,valueB=5
reduceResult=240
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值