kotlin<第十篇>:Flow-異步流

Flow: 是一種類似于序列的冷流,flow構(gòu)建器中的代碼直到流被收集的時(shí)候才運(yùn)行。
流的連續(xù)性:流的每次單獨(dú)收集都是按順序執(zhí)行的,除非使用特殊操作符。
從上游到下游每個(gè)過渡操作符都會處理每個(gè)發(fā)射出的值,然后再交給末端操作符。

flow構(gòu)建器創(chuàng)建一個(gè)函數(shù)
返回多個(gè)值,而且是異步的,不是一次性返回

(1)構(gòu)建流的三種方式

// flow構(gòu)建器創(chuàng)建一個(gè)函數(shù)
// 返回多個(gè)值,而且是異步的,不是一次性返回
suspend fun simpleFlow() = flow<Int> {
    for (i in 1..3) {
        delay(1000)
        emit(i) // 發(fā)射,產(chǎn)生一個(gè)元素
    }
}
runBlocking {
    // Flow構(gòu)建方式1
    simpleFlow().collect { value -> println(value) } // 收集元素

    // Flow構(gòu)建方式2
    (1..5).asFlow().filter {
        it % 2 == 0
    }.map {
        println("Map $it")
    }.onEach {
        delay(1000)
    }.collect {
        println("Collect $it")
    }

    // Flow構(gòu)建方式3
    flowOf("one", "two", "three").onEach { delay(1000) }.collect { values ->
        println(values)
    }
}

(2)流的上下文

    // Flow上下文驗(yàn)證
    (1..5).asFlow().filter {
        println("當(dāng)前線程-filter:" + Thread.currentThread().name)
        it % 2 == 0
    }.map {
        println("當(dāng)前線程-map:" + Thread.currentThread().name)
    }.onEach {
        delay(1000)
    }.collect {
        println("當(dāng)前線程-collect:" + Thread.currentThread().name)
        println("Collect $it")
    }

從打印結(jié)果上看,上游和下游都是在主線程。
但是,一般情況下,F(xiàn)low構(gòu)建之后的代碼塊中是耗時(shí)操作,所以不能放在主線程,解決方案是:在Flow構(gòu)建器后面添加 flowOn(Dispatchers.Default),改造后的代碼如下:

suspend fun simpleFlow() = flow<Int> {
    for (i in 1..3) {
        delay(1000)
        emit(i) // 發(fā)射,產(chǎn)生一個(gè)元素
    }
}.flowOn(Dispatchers.Default)

fun main() {
    runBlocking {
        // Flow構(gòu)建方式1
        simpleFlow().collect { value -> println(value) } // 收集元素

        // Flow構(gòu)建方式2
        (1..5).asFlow().filter {
            println("當(dāng)前線程-filter:" + Thread.currentThread().name)
            it % 2 == 0
        }.map {
            println("當(dāng)前線程-map:" + Thread.currentThread().name)
        }.onEach {
            delay(1000)
        }.flowOn(Dispatchers.Default).collect {
            println("當(dāng)前線程-collect:" + Thread.currentThread().name)
            println("Collect $it")
        }

        // Flow構(gòu)建方式3
        flowOf("one", "two", "three").flowOn(Dispatchers.Default).onEach { delay(1000) }.collect { values ->
            println(values)
        }

    }
}

(3)啟動(dòng)流

啟動(dòng)流:launchIn傳入?yún)f(xié)程作用域形參,使用launchIn替換collect我們可以在指定協(xié)程中啟動(dòng)流的收集

    (1..5).asFlow().onEach {
        delay(1000)
    }.flowOn(Dispatchers.Default).launchIn(CoroutineScope(Dispatchers.IO)).join()


    (1..5).asFlow().onEach {
        delay(1000)
    }.flowOn(Dispatchers.Default).launchIn(this).join()

(4)流的取消

使用 withTimeoutOrNull 方式取消:

suspend fun simpleFlow() = flow<Int> {
    for (i in 1..3) {
        delay(1000)
        emit(i) // 發(fā)射,產(chǎn)生一個(gè)元素
    }
}.flowOn(Dispatchers.Default)

fun main() {
    runBlocking {

        withTimeoutOrNull(2000) {
            // Flow構(gòu)建方式1
            simpleFlow().collect { value -> println(value) } // 收集元素
        }

        withTimeoutOrNull(2000) {
            (1..5).asFlow().onEach {
                delay(1000)
            }.flowOn(Dispatchers.Default).collect {
                println("Collect $it")
            }
        }

        withTimeoutOrNull(2000) {
            flowOf("one", "two", "three").flowOn(Dispatchers.Default).onEach { delay(1000) }.collect { values ->
                println(values)
            }
        }

        withTimeoutOrNull(2000) {
            (1..5).asFlow().onEach {
                delay(1000)
            }.flowOn(Dispatchers.Default).launchIn(CoroutineScope(Dispatchers.IO)).join()
        }

        println("Done...")

    }
}

另外,啟動(dòng)流還可以調(diào)用 cancelAndJoin 取消。

    val job = (1..5).asFlow().onEach {
        delay(1000)
    }.flowOn(Dispatchers.Default).launchIn(CoroutineScope(Dispatchers.IO))
    delay(1000)
    job.cancelAndJoin()

(5)流的取消檢測

為方便起見,流構(gòu)建器對每個(gè)發(fā)射值執(zhí)行附加的ensureActive 檢測以進(jìn)行取消,這意味著從 flow{...} 發(fā)出的繁忙循環(huán)是可以取消的。
出于性能原因,大多數(shù)其他流操作不會自行執(zhí)行其他取消檢測,在協(xié)程處于繁忙循環(huán)的情況下,必須明確檢測是否取消。
通過cancellable操作符來執(zhí)行此操作。

suspend fun simpleFlow() = flow<Int> {
    for (i in 1..5) {
        delay(1000)
        emit(i) // emit自帶檢測是否取消的能力
    }
}.flowOn(Dispatchers.Default)

fun main() {
    runBlocking {

        // emit 自帶檢測是否取消的能力
        simpleFlow().collect { value ->
            if (value == 3) cancel()
        }

        // 如果沒有emit,需要使用 cancellable
        (1..5).asFlow().cancellable().onEach {
            delay(1000)
        }.flowOn(Dispatchers.Default).collect { value ->
            if (value == 3) cancel()
        }

    }
}

(6)背壓

背壓:水流受到與流動(dòng)方向一致的壓力。
生產(chǎn)者、消費(fèi)者模式,只要生產(chǎn)效率 > 消費(fèi)效率,那么就會產(chǎn)生背壓。

處理背壓的方式有:

  • buffer(),并發(fā)運(yùn)行流中發(fā)射元素的代碼
  • conflate(),合并發(fā)射項(xiàng),不對每個(gè)值進(jìn)行處理
  • collectLatest(),取消并重新發(fā)射最后一個(gè)值
  • 當(dāng)必須更改CoroutineDispatcher時(shí),flowOn操作符使用了相同的緩沖機(jī)制,但是buffer函數(shù)顯示地請求緩沖而不改變執(zhí)行上下文
suspend fun simpleFlow() = flow<Int> {
    for (i in 1..50) {
        println("發(fā)送數(shù)據(jù):$i")
        delay(100)
        emit(i)
    }
}

fun main() {
    runBlocking {
        val time = measureTimeMillis {
            simpleFlow()
                .collect { value ->
                delay(300)
                println("接收數(shù)據(jù):$value")
            }
        }
        println("耗時(shí):$time")
    }
}

以上代碼,發(fā)送數(shù)據(jù)和接收數(shù)據(jù)都是在同一個(gè)線程中并行執(zhí)行,如果存在耗時(shí)程序,將特別影響效率。

為了增加執(zhí)行效率,可以使用 buffer 設(shè)置緩存大小,從而起到加快執(zhí)行速率的效果。

    val time = measureTimeMillis {
        // 背壓
        simpleFlow()
            .buffer(10)
            .collect { value ->
            delay(300)
            println("接收數(shù)據(jù):$value")
        }
    }

但是,從生產(chǎn)者/消費(fèi)者的設(shè)計(jì)思想的角度上考慮,發(fā)送數(shù)據(jù)最好放在子線程。

    val time = measureTimeMillis {
        // 背壓
        simpleFlow()
            .flowOn(Dispatchers.Default)
            .collect { value ->
            delay(300)
            println("接收數(shù)據(jù):$value")
        }
    }

使用 flowOn 可以指定 Flow 的協(xié)程作用域,這樣可以將 并行 轉(zhuǎn)成 并發(fā),從而加快執(zhí)行效率。

runBlocking {
    val time = measureTimeMillis {
        // 背壓
        simpleFlow()
            .conflate()
            .collect { value ->
            delay(300)
            println("接收數(shù)據(jù)==:$value")
        }
    }
    println("耗時(shí):$time")
}

以上代碼使用 conflate,中間一些元素不會處理,從而加快執(zhí)行效率。

    val time = measureTimeMillis {
        // 背壓
        simpleFlow()
            .collectLatest { value ->
            delay(300)
            println("接收數(shù)據(jù)==:$value")
        }

以上代碼將 collect 改成 collectLatest 之后,只會處理最后一個(gè)值,從而加速執(zhí)行速度。

(7)轉(zhuǎn)換操作符

使用map轉(zhuǎn)換:

suspend fun simpleFlow() = flow<Int> {
    for (i in 1..3) {
        println(i)
        emit(i)
    }
}

fun main() {
    runBlocking {
        simpleFlow()
            .map { value ->
                "response $value"
            }
            .collect { value ->
                println(value)
            }
    }
}

使用transform轉(zhuǎn)換,可以轉(zhuǎn)換成任意次、任意值的Flow:

suspend fun simpleFlow() = flow<Int> {
    for (i in 1..3) {
        println(i)
        emit(i)
    }
}

fun main() {
    runBlocking {
        simpleFlow()
            .transform { request ->
                emit("request $request")
                emit("request $request")
            }
            .collect { value ->
                println(value)
            }
    }
}

(8)限長操作符

take 是限長操作符,可以限制處理的數(shù)量:

suspend fun simpleFlow() = flow<Int> {
    for (i in 1..3) {
        println(i)
        emit(i)
    }
}

fun main() {
    runBlocking {
        simpleFlow()
            .take(2)
            .collect { value ->
                println(value)
            }
    }
}

(9)末端操作符

末端操作符是在流上用于 啟動(dòng)流收集的掛起函數(shù)。collect是最基礎(chǔ)的末端操作符,但是還有另外一些更加方便使用的末端操作符:

  • 轉(zhuǎn)化為各種集合,例如:toList與toSet。
  • 獲取第一個(gè)(first)值與確保流發(fā)射單個(gè)(single)值的操作符。
  • 使用reduce與fold將流規(guī)約到單個(gè)值。
fun main() {
    runBlocking {
        val sum = simpleFlow()
            .reduce { a, b ->
                a + b
            }
        println(sum)
    }
}

reduce 操作符可以將元素累加。
reduce的返回值類型必須和集合的元素類型相符。

suspend fun simpleFlow() = flow<Int> {
    for (i in 1..3) {
        emit(i)
    }
}

fun main() {
    runBlocking {
        val newStr = simpleFlow()
            .fold(StringBuilder()) { str: StringBuilder, a: Int ->
                str.append(a).append(" ")
            }
        println(newStr)
    }
}

而fold的返回值類型則不受約束。

(10)組合操作符

zip 操作符將兩個(gè)流合并。

runBlocking {
    val nums1 = (1..3).asFlow()
    val nums2 = flowOf("one", "two", "three")
    nums1.zip(nums2) {a, b ->
        "$a $b"
    }.collect {value->
        println(value)
    }
}

(11)展平操作符

流表示異步接收的值序列,所以很容易遇到這種情況:每個(gè)值都會觸發(fā)對另一個(gè)值序列的請求,然而,由于流具有異步的性質(zhì),因此需要不同的展平模式,為此,存在一系列的流展平操作符:

  • flatMapConcat:連接模式
  • flatMapMerge:合并模式
  • flatMapLatest: 最新展平模式
suspend fun requestFlow(i: Int) = flow<String> {
    emit("request $i first")
    delay(500)
    emit("request $i second")
}

fun main() {
    runBlocking {
        val startTime = System.currentTimeMillis()
        (1..3).asFlow()
            .onEach { delay(100) }
            .flatMapConcat {
                requestFlow(it) // Flow的元素是Flow
            }
            .collect { value->
            println("$value -- ${System.currentTimeMillis() - startTime}")
        }
    }
}

代碼中 flatMapConcat 可以換成 flatMapMerge 或者 flatMapLatest

三者的執(zhí)行結(jié)果是:

flatMapConcat :(requestFlow全部執(zhí)行完)

request 1 first -- 198
request 1 second -- 701
request 2 first -- 815
request 2 second -- 1319
request 3 first -- 1428
request 3 second -- 1932

flatMapMerge:(不需要等待requestFlow全部執(zhí)行完)

request 1 first -- 281
request 2 first -- 361
request 3 first -- 470
request 1 second -- 798
request 2 second -- 876
request 3 second -- 985

flatMapLatest:

request 1 first -- 250
request 2 first -- 376
request 3 first -- 485
request 3 second -- 1001

(12)流的異常處理

suspend fun requestFlow() = flow<Int> {
    for (i in 1..3) {
        emit(i)
        throw RuntimeException("exception")
    }
}.catch {e: Throwable ->
    println("上游異常捕獲:" + e.message)
}

fun main() {
    runBlocking {
        try {
            requestFlow()
                .collect { value->
                    check(value < 2) // 檢查異常
                    println(value)
                }
        } catch (e: Throwable) {
            println("下游異常捕獲:" + e.message)
        }
    }
}

check:檢查異常,一旦檢查到異常,程序crash。
下游通過 try...catch 捕獲異常,上游Flow自帶 catch 函數(shù)。

(13)流的完成

收集完成時(shí),使用 finally,表示收集完成。

suspend fun requestFlow() = flow<Int> {
    for (i in 1..3) {
        emit(i)
    }
}

fun main() {
    runBlocking {
        try {
            requestFlow().collect { value-> println(value) }
        } finally {
            println("...完成...")
        }
    }
}

使用 onCompletion 也可以表示完成:

suspend fun requestFlow() = flow<Int> {
    for (i in 1..3) {
        emit(i)
        throw RuntimeException("exception")
    }
}.catch {exception->
    println("catch -> exception:" + exception.message)
}

fun main() {
    runBlocking {

        requestFlow()
            .onCompletion {exception ->
                if (exception != null) { // 異常導(dǎo)致完成
                    println("finish -> exception:" + exception.message)
                } else { // 正常結(jié)束
                    println("正常結(jié)束")
                }
            }
            .collect { value-> println(value) }

    }
}

onCompletion 可以拿到異常信息,但是不能捕獲異常。

(13)Flow實(shí)現(xiàn)多路復(fù)用

多數(shù)情況下,我們可以通過構(gòu)造合適的Flow來實(shí)現(xiàn)多路復(fù)用的效果。

data class User(val name: String)
data class Response<T>(val value: T, val isLocal: Boolean)
suspend fun CoroutineScope.getUserForLocal(name: String) = async {
    delay(1000)
    User(name)
}

suspend fun CoroutineScope.getUserFromRemote(name: String) = async {
    delay(100)
    User(name)
}

fun main() {
    runBlocking {
        val name = "guest"
        // 兩個(gè)函數(shù)
        listOf(::getUserForLocal, ::getUserFromRemote)
            .map { function->
                function.call(name)
            }
            .map { deferred ->
                flow { emit(deferred.await()) }
            }.merge().collect { user -> println(user) }

    }
}

以上代碼用到了反射,需要引入依賴:

implementation 'org.jetbrains.kotlin:kotlin-reflect:1.0.6'

[完...]

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,973評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內(nèi)容