协程系列(六)异步flow

1,487 阅读3分钟

本文基于协程官方文档讲解,具体可查看here

一、 Flow

1.1、在讲flow前,我们先看Sequence

fun printMsg(msg:String){
   println("${Thread.currentThread().name}  "+msg)
}
fun simple() = sequence<Int> {
    for (i in 1..3) {
        Thread.sleep(300)
        yield(i) //yield下一个值
    }
}
fun main() {
    simple().forEach { value -> printMsg("${value}") }
}

image.png Sequence这里的yield并不是我们之前说的yield(同时并发多个协程,可以让其他协程先执行),这里的yield为正在构建的Iterator生成一个值,并挂起直到请求下一个值。 sequence的遍历需要搭配yield。

1.2、Flow的基本使用

flow的话,需要搭配emit使用。

fun simple() = flow<Int> {
    for (i in 1..3) {
        delay(300)
        emit(i) //emit下一个值
    }
}
fun main() = runBlocking {
    simple().collect { value->
        printMsg("${value}")
    }
}

image.png Flow是冷流,当collect时会触发,类似于sequence的末端操作触发

1.3、 flow响应取消

fun simple() = flow<Int> {
    for (i in 1..4) {
        Thread.sleep(100)
        emit(i) //emit下一个值
    }
}
fun main() = runBlocking {
    withTimeoutOrNull(250){ //超时取消
        simple().collect { value-> printMsg("${value}") }
    }
    printMsg("Done")
}

image.png flow伴随flow所在的协程取消,它就自动取消了。后面也会介绍flow的主动取消。

1.4、flow的builder操作

fun main() = runBlocking {
    (1..3).asFlow().collect { value -> printMsg("${value}") }
    printMsg("Done")
}

image.png 区间或list都可以使用asFlow转成flow,为啥这里没有使用emit,是因为区间的值是固定的,无需使用emit。

1.5、中间的flow操作

fun main() = runBlocking {
    val list = listOf(1, 2, 3, 4)
    list.asFlow().map { value -> value + 1 }
                 .collect { value -> printMsg("${value}") }
    
     printMsg("----------------")
     
    list.asFlow().transform { value -> emit(value + 1) }
                 .collect { value -> printMsg("${value}") }
}

image.png

flow本身就支持map,跟Rxjava没区别,这里的transform很有用,可以在其block里面去emit值,改变collect消费的数据,但是在map的block中没法用emit发射新数据,大部分情况下,可以用transform去取代map。

问题: list本身就有map操作,为啥还要转成flow来做呢?

fun main() = runBlocking {
    val list = listOf(1, 2, 3, 4)
    list.asFlow().map { value ->
        printMsg("flow map $value")
        value + 1
    }.collect { value ->
        printMsg("${value}") }

    printMsg("----------------")

    list.map { value ->
        printMsg("list map $value")
        value + 1
    }.forEach { value ->
        printMsg("${value}") }
}

image.png flow的map效率会高些,为啥,因为flow没有产生中间集合,list的map会有中间集合产生,所以建议转成flow,早些年是建议用sequence来做,因sequence的map也不会产生中间集合。

1.6、size限制操作使用take操作符

fun main() = runBlocking {
    (1..3).asFlow()
         .take(2)
        .collect { value ->
            printMsg("${value}")
        }
    printMsg("-----------")
}

image.png

1.7、flow的末端操作 reduce操作符

fun main() = runBlocking {
    val sum = (1..5).asFlow().map { it * it }
        .reduce { a, b ->
            printMsg("${a}     ${b}")
             a + b     //执行累加
        }
    printMsg("$sum")
}

image.png

1.8、flow的context

fun main() = runBlocking {
    launch(Dispatchers.Default) {
        flow{
            for(i in 1..3){
                emit(i)
                printMsg("${i}   emit")
            }
        }.filter {
                printMsg("${it}   filter")
                it % 2 == 0
            }.map {
                printMsg("${it}   map")
                it * it
            }.collect {
                printMsg("${it}   collect")
            }
    }
    printMsg("main ")
}

image.png 我们称emit发射所在的block为上游,下面的filter、map、collect都称为下游,现在上下游的执行都在同一个线程里面。如想让上游发射在一个线程,下游在另外一个线程。我们可以使用flowOn来指定上游发射所在的线程。

fun main() = runBlocking {
    launch(Dispatchers.Default) {  // 对应rxjava的observeOn
        flow{
            for(i in 1..3){
                emit(i)
                printMsg("${i}   emit")
            }
        }.flowOn(Dispatchers.IO)  // 对应rxjava的subscribeOn
            .filter {
                printMsg("${it}   filter")
                it % 2 == 0
            }.map {
                printMsg("${it}   map")
                it * it
            }.collect {
                printMsg("${it}   collect")
            }
    }
    printMsg("main ")
}

image.png flow的flowOn就类似于Rxjava的subscribeOn, launch指定的调度器就相当于Rxjava的observeOn。

1.9、flow的buffer操作符可节省时间

当我们上游发射快,下游消费慢的时候,可以使用buffer来处理,可节省时间。
先看没加buffer的情况:👇

fun main() = runBlocking {
    launch {
        val time= measureTimeMillis {
            flow{
                for(i in 1..3){
                    delay(300)
                    emit(i)
                }
            }.collect {
                delay(2000)
                printMsg("${it}")
            }
        }
        printMsg("耗时 ${time}")
    }
    printMsg("main")
}

image.png

fun main() = runBlocking {
    launch {
        val time = measureTimeMillis {
            flow {
                for (i in 1..3) {
                    delay(300)
                    emit(i)
                }
            }.buffer()   //添加buffer的情况
                .collect {
                    delay(2000)
                    printMsg("${it}")
                }
        }
        printMsg("耗时 ${time}")
    }
    printMsg("main")
}

image.png 只是少了两次发射的时间而已。

1.10、flow的conflation

conflation合并之意,会跳过中间的值,仅仅发送最近的值.

fun main() = runBlocking {
    launch {
        val time = measureTimeMillis {
            flow {
                for (i in 1..5) {
                    delay(300)
                    emit(i)
                }
            }.conflate()
                .collect {
                    delay(2000)
                    printMsg("${it}")
                }
        }
        printMsg("耗时 ${time}")
    }
    printMsg("main")
}

image.png 中间的2,3,4都丢了,仅仅发射最近的值1和5。

1.11、flow的collectLatest

collectLatest并不仅仅只是消费最新的值,而且还能中断之前的collect操作。

fun main() = runBlocking {
    launch {
        val time = measureTimeMillis {
            flow {
                for (i in 1..5) {
                    delay(300)
                    emit(i)
                }
            }.collectLatest {
                    delay(2000)
                    printMsg("${it}")
                }
        }
        printMsg("耗时 ${time}")
    }
    printMsg("main")
}

image.png 我们在collect里面多添加一行打印:

fun main() = runBlocking {
    launch {
        val time = measureTimeMillis {
            flow {
                for (i in 1..5) {
                    delay(300)
                    emit(i)
                }
            }.collectLatest {
                printMsg("${it} before")  //添加一行这个
                delay(2000)
                printMsg("${it}  after")
            }
        }
        printMsg("耗时 ${time}")
    }
    printMsg("main")
}

image.png 前面消费1,2,3,4的collect中的delay全部中断了,只有5成功了。

1.12、flow的Zip和combine (合并操作符)

fun main() = runBlocking {
    val num = (1..3).asFlow().onEach { delay(200) }
    val strs = flowOf("one", "two", "three").onEach { delay(300) }
    num.zip(strs) { a, b -> "${a}  ${b}" }.collect {   //使用zip
        printMsg("${it}")
    }
}

image.png

fun main() = runBlocking {
    val num = (1..3).asFlow().onEach { delay(200) }
    val strs = flowOf("one", "two", "three").onEach { delay(300) }
    num.combine(strs) { a, b -> "${a}  ${b}" }.collect {   //使用combine
        printMsg("${it}")
    }
}

image.png zip跟combine用法这里是一样的,可能zip更符合我们合并的意图。

1.13、3种模式的flatten操作

flatten其实就是压平的操作,多个数组或者list打平成一个。

1.13.1、 flatMapConcat

fun main() = runBlocking {
    (1..3).asFlow().flatMapConcat { it->
        flow{     //这里会有3个flow
            emit("$it first" )
            delay(500L)
            emit("$it second" )
        }
    }.collect{ it->
        printMsg("$it")
    }
    printMsg("Done")
}

image.png

1.13.2、 flatMapMerge

fun main() = runBlocking {
    (1..3).asFlow().flatMapMerge { it->
        flow{
            emit("$it first" )
            delay(500L)
            emit("$it second" )
        }
    }.collect{ it->
        printMsg("$it")
    }
    printMsg("Done")
}

image.png

1.13.3、 flatMapLatest

只能完整的收到最近的数据。

fun main() = runBlocking {
    (1..3).asFlow().flatMapLatest{ it->
        flow{
            emit("$it first" )
            delay(500L)
            emit("$it second" )
        }
    }.collect{ it->
        printMsg("$it")
    }
    printMsg("Done")
}

image.png

1.14、flow的异常

可以在flow外层直接使用try catch捕捉。

fun main() = runBlocking {
    try {
        (1..3).asFlow()
            .collect { it ->
                printMsg("$it")
                if (it > 1) {
                    throw IllegalArgumentException("")
                }
            }
    }catch (e:Throwable){
        printMsg("catch $e")
    }
}

image.png 也可以使用catch操作捕捉。

fun main() = runBlocking {
    flow {
        for (i in 1..3) {
            emit(i)
            if (i > 1) {
                throw IllegalArgumentException("xxxxx")
            }
        }
    }.catch { e -> emit(0) }   //catch
        .collect { it ->
            printMsg("$it")
        }

}

image.png 注意这里的catch操作只能捕捉上游发射异常,不能捕捉下游消费的异常

fun main() = runBlocking {
    flow {
        for (i in 1..3) {
            emit(i)
        }
    }.catch { e -> emit(0) }
        .collect { it ->
            if (it > 1) {
                throw IllegalArgumentException("xxxxx")
            }
            printMsg("$it")
        }
}

image.png catch来捕获emit的异常,至于collect的异常还得外层的try catch捕捉。

1.15、flow的完成

try finally和onCompletion都是可以捕捉完成时机。

1.15.1、try finally

fun main() = runBlocking {
    try {
        flow {
            for (i in 1..3) {
                emit(i)
            }
        }.collect { it ->
            printMsg("$it")
        }
    } finally {
        printMsg("Done")
    }
}

image.png

1.15.2、onCompletion

fun main() = runBlocking {
    flow {
        for (i in 1..3) {
            printMsg("emit $i")
            emit(i)
        }
    }.onCompletion { printMsg("onComplete") }
        .collect { it ->
            printMsg("$it")
        }
}

image.png

onCompletion :可以带参数,也可以不带,带参数的话,可以判断是否是异常的(不论上游还是下游,只有有异常,都是非null的)

上游发射抛异常的情形:👇

fun main() = runBlocking {
    flow {
        for (i in 1..3) {
            printMsg("emit $i")
            emit(i)
            throw IllegalArgumentException("xxxxx")
        }
    }.onCompletion { cause -> if (cause != null) printMsg("${cause.message}") }
        .onCompletion { printMsg("onComplete") }
        .catch { it -> printMsg("catch exception ${it.message} ") }
        .collect { it ->
            printMsg("$it")
        }
}

image.png

下游消费抛异常的情形:👇

fun main() = runBlocking {
    flow {
        for (i in 1..3) {
            printMsg("emit $i")
            emit(i)
        }
    }.onCompletion { cause -> if (cause != null) printMsg("${cause.message}") }
        .onCompletion { printMsg("onComplete") }
        .catch { it -> printMsg("catch exception ${it.message} ") }
        .collect { it ->
            throw IllegalArgumentException("xxxxx")
            printMsg("$it")
        }
}

image.png

1.16、flow的启动

flow的启动触发除了collect以外,还有launchIn.

fun main() = runBlocking {
    flow {
        for (i in 1..3) {
            printMsg("emit $i")
            emit(i)

        }
    }.onEach { delay(100L) }
        .onEach { event -> printMsg("$event") }
        .launchIn(this)   //触发flow的启动
    printMsg("main")
}

image.png

1.17、flow的主动取消

需设置cancellable() + 操作cancel(),同时flow所在的协程也会取消。

fun main() = runBlocking {
    launch {
        flow {
            for (i in 1..3) {
                emit(i)
            }
        }.cancellable()
            .collect { event ->
            if (event == 2) cancel()
            printMsg("$event     ${coroutineContext[Job]?.isActive}")
        }
    }
    printMsg("main")
}

image.png