一、表示多個值
Kotlin 中可以使用集合來表示多個值
1.序列
fun simple(): Sequence<Int> = sequence {//序列構建器
for (i in 1..3) {
Thread.sleep(100)
yield(i)
}
}
fun main() {
simple().forEach { value -> println(value) }
}
2.掛起函數
計算過程阻塞運行該代碼的主線程。當這些值由異步代碼計算時,我們可以使用 suspend 修飾符標記函數 simple ,這樣它就可以在不阻塞的情況下執行其工作并將結果作為列表返回:
suspend fun simple(): List<Int> {
delay(1000)
return listOf(1, 2, 3)
}
fun main() = runBlocking {
simple().forEach { value -> println(value) }
}
3.流
為了表式異步計算的值流(stream),我們可以使用 Flow 類型(正如同步計算值會使用 Sequence 類型):
fun simple(): Flow<Int> = flow { // 流構建器
for (i in 1..3) {
delay(100)
emit(i) // 發送下一個值
}
}
fun main() = runBlocking {
// 啟動并發的協程以驗證主線程并未阻塞
launch {
for (k in 1..3) {
println("I'm not blocked $k")
delay(100)
}
}
simple().collect { value -> println(value) }
}
輸出:
I'm not blocked 1
1
I'm not blocked 2
2
I'm not blocked 3
3
二、流是冷的
Flow 是?種類似于序列的冷流 。 flow 構建器中的代碼直到流被收集的時候才運行。
三、流取消基礎
流采用與協程同樣的協作取消。
四、流構建器
flow { ... } 構建器是最基礎的?個。還有構建器使流的聲明更簡單:
- flowOf 構建器定義了?個發射固定值集的流。
- 使用 .asFlow() 擴展函數,可以將各種集合與序列轉換為流。
五、過度流操作符
過渡操作符應用于上游流,并返回下游流。這些操作符也是冷操作符,就像流?樣。這類操作符本身不是掛起函數。它運行的速度很快,返回新的轉換流的定義。
流與序列的主要區別在于這些操作符中的代碼可以調用掛起函數。
suspend fun performRequest(request: Int): String {
delay(1000)
return "response $request"
}
fun main() = runBlocking {
(1..3).asFlow()
.map { request -> performRequest(request) }
.collect { response -> println(response) }
}
輸出:
response 1
response 2
response 3
1.轉換操作符
在流轉換操作符中,最通用的?種稱為 transform。它可以用來模仿簡單的轉換,例如 map 與 filter,以及實施更復雜的轉換。使用 transform 操作符,我們可以發射任意值任意次。
使用 transform 我們可以在執行長時間運行的異步請求之前發射?個字符串并跟蹤這個響應。
suspend fun performRequest(request: Int): String {
delay(1000)
return "response $request"
}
fun main() = runBlocking {
(1..3).asFlow()
.transform { request ->
emit("Making request $request")
emit(performRequest(request))
}
.collect { response -> println(response) }
}
輸出:
Making request 1
response 1
Making request 2
response 2
Making request 3
response 3
2.限長操作符
限長過渡操作符(例如 take)在流觸及相應限制的時候會將它的執行取消。協程中的取消操作總是通過拋出異常來執行(不會在控制臺輸出對賬信息,被取消的協程中 CancellationException 被認為是協程執行結束的正常原因),不會奔潰這樣所有的資源管理函數(如 try {...} finally {...} 塊)會在取消的情況下正常運行:
fun numbers(): Flow<Int> = flow {
try {
emit(1)
emit(2)
println("This line will not execute")
emit(3)
} finally {
println("Finally in numbers")
}
}
fun main() = runBlocking {
numbers()
.take(2)
.collect { value -> println(value) }
}
輸出:
1
2
Finally in numbers
六、末端流操作符
末端操作符是在流上用于啟動流收集的掛起函數。collect 是最基礎的末端操作符,還有?些其它末端操作符:
- 轉化為各種集合,例如 toList 與 toSet
- 獲取第?個(first)值與確保流發射單個(single)值的操作符。single空流拋出NoSuchElementException,對于包含多個元素的流拋出IllegalStateException。
- 使用 reduce 與 fold 將流規約到單個值。
fun main() = runBlocking {
val sum = (1..5).asFlow()
.map { it * it }
// .first()//輸出1
// .reduce { a, b -> a + b } //輸出55
.fold(1) { a, b -> a + b } //帶初始值的累加輸出56
println(sum)
}
七、流是連續的
流的每次單獨收集都是按順序執行的,除非進行特殊操作的操作符使用多個流。默認情況下不啟動新協程。從上游到下游每個過渡操作符都會處理每個發射出的值,然后再交給末端操作符。
八、流上下文
流的收集總是在調用協程的上下文中發生。該屬性稱為上下文保存 。所以默認的,flow { ... } 構建器中的代碼運行在相應流的收集器提供的上下文中。
fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
fun simple(): Flow<Int> = flow {
log("Started simple flow")
for (i in 1..3) {
emit(i)
}
}
fun main() = runBlocking {
simple().collect { value -> log("Collected $value") }
}
輸出:
[main @coroutine#1] Started simple flow
[main @coroutine#1] Collected 1
[main @coroutine#1] Collected 2
[main @coroutine#1] Collected 3
由于 simple().collect 是在主線程調用的,那么 simple 的流主體也是在主線程調用的。這是快速運行或異步代碼的理想默認形式,它不關心執行的上下文并且不會阻塞調用者。
1.withContext發出錯誤
長時間運行的消耗 CPU 的代碼也許需要在 Dispatchers.Default 上下文中執行,并且更新 UI 的代碼也許需要在 Dispatchers.Main 中執行。
通常,withContext 用于在 Kotlin 協程中改變代碼的上下文,但是 flow {...} 構建器中的代碼必須遵循上下文保存屬性,并且不允許從其他上下文中發射(emit)。
2.flowOn操作符
flowOn 函數用于更改流發射的上下文。
fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
fun simple(): Flow<Int> = flow {
for (i in 1..3) {
Thread.sleep(100)
log("Emitting $i")
emit(i)
}
}.flowOn(Dispatchers.Default)
fun main() = runBlocking {
simple().collect { value ->
log("Collected $value")
}
}
輸出:
[DefaultDispatcher-worker-1 @coroutine#2] Emitting 1
[main @coroutine#1] Collected 1
[DefaultDispatcher-worker-1 @coroutine#2] Emitting 2
[main @coroutine#1] Collected 2
[DefaultDispatcher-worker-1 @coroutine#2] Emitting 3
[main @coroutine#1] Collected 3
flowOn 操作符已改變流的默認順序性。現在收集發生在?個協程中 (“coroutine#1”)而發射發生在運行于另?個線程中與收集協程并發運行的另?個協程(“coroutine#2”)中。當上游流必須改變其上下文中的 CoroutineDispatcher 的時候,flowOn 操作符創建了另?個協程。
九、緩沖
buffer 操作符來并發運行流中發射元素的代碼以及收集的代碼,而不是順序運行它們:
fun simple(): Flow<Int> = flow {
for (i in 1..3) {
delay(100)
emit(i)
}
}
fun main() = runBlocking {
val time = measureTimeMillis {
simple()
.buffer()
.collect { value ->
delay(300)
println(value)
}
}
println("Collected in $time ms")
}
輸出:
1
2
3
Collected in 1098 ms
僅需要等待第?個數字產生的 100 毫秒以及處理每個數字各需花費的 300 毫秒
1.合并
當流代表部分操作結果或操作狀態更新時,可能沒有必要處理每個值,而是只處理最新的那個。
當收集器處理它們太慢的時候,conflate 操作符可以用于跳過中間值。
fun main() = runBlocking {
val time = measureTimeMillis {
simple()
.conflate()
.collect { value ->
delay(300)
println(value)
}
}
println("Collected in $time ms")
}
輸出:
1
3
Collected in 779 ms
2.處理最新值
當發射器和收集器都很慢的時候,合并是加快處理速度的?種方式。它通過刪除發射值來實現。另?種方式是取消緩慢的收集器,并在每次發射新值的時候重新啟動它。有?組與 xxx 操作符執行相同基本邏輯的 xxxLatest 操作符,但是在新值產生的時候取消執行其塊中的代碼。
如collecLatest
fun main() = runBlocking {
val time = measureTimeMillis {
simple()
.collectLatest { value ->
println("Collecting $value")
delay(300)
println("Done $value")
}
}
println("Collected in $time ms")
}
輸出:
Collecting 1
Collecting 2
Collecting 3
Done 3
Collected in 767 ms
十、組合多個流
1.zip
zip 操作符用于組合兩個流中的相關值
fun main() = runBlocking {
val nums = (1..3).asFlow()
val strs = flowOf("one", "two", "three")
nums.zip(strs) { a, b -> "$a -> $b" }
.collect { println(it) }
}
輸出:
1 -> one
2 -> two
3 -> three
2.combine
當流表示?個變量或操作的最新值時,可能需要執行計算,參考合并操作符conflate。這依賴于相應流的最新值,并且每當上游流產生值的時候都需要重新計算。這種相應的操作符家族稱為combine。
fun main() = runBlocking {
val nums = (1..3).asFlow().onEach { delay(300) }
val strs = flowOf("one", "two", "three").onEach { delay(400) }
val startTime = currentTimeMillis()
nums.combine(strs) { a, b -> "$a -> $b" }
.collect { value ->
println("$value at ${currentTimeMillis() - startTime} ms from start")
}
}
輸出:
1 -> one at 452 ms from start
2 -> one at 656 ms from start
2 -> two at 858 ms from start
3 -> two at 967 ms from start
3 -> three at 1265 ms from start
十一、展平流
1.flatMapConcat
連接模式由 flatMapConcat 與 flattenConcat 操作符實現。它們是相應序列操作符最相近的類似物。它們在等待內部流完成之后開始收集下?個值。
fun requestFlow(i: Int): Flow<String> = flow {
emit("$i: First")
delay(500)
emit("$i: Second")
}
fun main() = runBlocking {
val startTime = currentTimeMillis()
(1..3).asFlow().onEach { delay(100) }
.flatMapConcat { requestFlow(it) }
.collect { value ->
println("$value at ${currentTimeMillis() - startTime} ms from start")
}
}
輸出:
1: First at 134 ms from start
1: Second at 649 ms from start
2: First at 758 ms from start
2: Second at 1270 ms from start
3: First at 1379 ms from start
3: Second at 1889 ms from start
2.flatMapMerge
另?種展平模式是并發收集所有傳入的流,并將它們的值合并到?個單獨的流,以便盡快的發射值。它由 flatMapMerge 與 flattenMerge 操作符實現。他們都接收可選的用于限制并發收集的流的個數的 concurrency 參數。
fun main() = runBlocking {
val startTime = currentTimeMillis()
(1..3).asFlow().onEach { delay(100) }
.flatMapMerge { requestFlow(it) }
.collect { value ->
println("$value at ${currentTimeMillis() - startTime} ms from start")
}
}
輸出:
1: First at 175 ms from start
2: First at 268 ms from start
3: First at 377 ms from start
1: Second at 685 ms from start
2: Second at 777 ms from start
3: Second at 888 ms from start
3.flatMapLatest
flatMapLatest與 collectLatest 操作符類似,在發出新流后立即取消先前流的收集。
fun main() = runBlocking {
val startTime = currentTimeMillis()
(1..3).asFlow().onEach { delay(100) }
.flatMapLatest { requestFlow(it) }
.collect { value ->
println("$value at ${currentTimeMillis() - startTime} ms from start")
}
}
輸出:
1: First at 160 ms from start
2: First at 334 ms from start
3: First at 445 ms from start
3: Second at 955 ms from start
十二、流異常
當運算符中的發射器或代碼拋出異常時,流收集可以帶有異常的完成。
1.收集
try 與 catch
2.透明性
流必須對異常透明,即在 flow { ... } 構建器內部的 try/catch 塊中發射值是違反異常透明性的。
透明捕獲
catch 過渡操作符遵循異常透明性,僅捕獲上游異常( catch 操作符上游的異常,但是它下面的不是)。如果 collect { ... } 塊(位于 catch 之下)拋出?個異常,那么異常會逃逸。
聲明式捕獲
可以將 catch 操作符的聲明性與處理所有異常的期望相結合,將 collect 操作符的代碼塊移動到 onEach 中,并將其放到 catch 操作符之前。收集該流必須調用無參的 collect() 。
十三、流完成
當流收集完成時(普通情況或異常情況),它可能需要執行?個動作。它可以通過兩種方式完成:命令式或聲明式。
- 命令式 finally 塊
- 聲明式處理,onCompletion 過渡操作符,它可以在流完全收集時調用
與 catch 操作符的另?個不同點是 onCompletion 能觀察到所有異常并且僅在上游流成功完成(沒有取消或失敗)的情況下接收?個 null 異常。
fun main() = runBlocking {
simple()
.onCompletion { cause -> println("Flow completed with $cause") }
.collect { value ->
check(value <= 1) { "Collected $value" }
println(value)
}
}
輸出:
1
Flow completed with java.lang.IllegalStateException: Collected 2
Exception in thread "main" java.lang.IllegalStateException: Collected 2//程序異常終止
十四、啟動流
launchIn末端操作符, 必要參數是 CoroutineScope ,指定哪個協程來啟動流的收集。
十五、流取消檢測
流構建器對每個發射值執行附加的 ensureActive 檢測以進行取消。這意味著從 flow { ... } 發出的繁忙循環是可以取消的:
fun foo(): Flow<Int> = flow {
for (i in 1..5) {
println("Emitting $i")
emit(i)
}
}
fun main() = runBlocking {
foo().collect { value ->
if (value == 3) cancel()
println(value)
}
}
輸出:
Emitting 1
1
Emitting 2
2
Emitting 3
3
Emitting 4
Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@2a742aa2//異常終止
出于性能原因,大多數其他流操作不會自動執行其他取消檢測。例如,如果使? IntRange.asFlow 擴展來編寫相同的繁忙循環。
必須明確檢測是否取消。可以添加 .onEach { currentCoroutineContext().ensureActive() } ,也可使用 cancellable 操作符來執行此操作。
fun main() = runBlocking {
(1..5).asFlow().cancellable().collect { value ->
if (value == 3) cancel()
println(value)
}
}
十六、流(flow)與響應式流(Reactive Streams)
Flow 依然是響應式流,設計上和RxJava相似。