記事ディレクトリ
流れの平坦化
このような呼び出しがあるとしましょう:
fun requestFlow(i: Int): Flow<String> = flow {
emit("$i: First")
delay(500) // wait 500 ms
emit("$i: Second")
}
(1..3).asFlow().map { requestFlow(it) }
この時点で、次のようなフローが得られますFlow<Flow<String>>
。最後に処理すると、1つのフローに押しつぶされます。Flow<String>
この目的のために、コレクションとシーケンスにはflatten演算子とflatMap演算子があります。ただし、フローは非同期であるため、異なるフラット化モードが必要であるため、フローには一連のフラット化演算子があります。
flatMapConcat
連結モードは、flatMapConcatおよびflattenConcatオペレーターによって実装されます。これらは、対応するシーケンス演算子の最も直接的な類似物です。次の例に示すように、内部プロセスが完了するのを待ってから、次の例の収集を開始します。
val startTime = System.currentTimeMillis() // remember the start time
(1..3).asFlow().onEach { delay(100) } // a number every 100 ms
.flatMapConcat { requestFlow(it) }
.collect { value -> // collect and print
println("$value at ${System.currentTimeMillis() - startTime} ms from start")
}
flatMapConcatの順序の性質は、出力から明確に見ることができます。
1:開始から最初に121 msで
1:開始から622 msで2番目:
開始から727 msで最初
2:開始から1227 msで2番目
3:開始から1328 msで最初
3:開始から1829 msで2番目
flatMapMerge
別のフラット化モードは、すべての着信ストリームを同時に収集し、それらの値を単一のストリームにマージして、値ができるだけ早く送信されるようにすることです。これは、flatMapMergeおよびflattenMergeオペレーターによって実装されます。どちらもオプションの並行性パラメーターを受け入れます。これは、同時に収集される並行ストリームの数を制限します(デフォルトでは16です)。
val startTime = System.currentTimeMillis() // remember the start time
(1..3).asFlow().onEach { delay(100) } // a number every 100 ms
.flatMapMerge { requestFlow(it) }
.collect { value -> // collect and print
println("$value at ${System.currentTimeMillis() - startTime} ms from start")
}
flatMapMergeの同時実行性は明らかです。
1:最初の開始から136ミリ秒
2:最初の開始から231ミリ秒
3:最初の開始から333ミリ秒
1:2番目の開始から639ミリ秒
2:2番目の開始から732ミリ秒
3:2番目の開始から833ミリ秒
音符、flatMapMergeの(この例では* {requestFlow(IT)}のコードブロックを呼び出すため)が、最初の実行順序に相当するストリームを得同時収集、地図{requestFlow(IT)} 次に、結果呼び出し flattenMergeを*
flatMapLatest
以前のcollectLatestオペレーターの紹介と同様に、フローをフラット化するときのflatMapLatestオペレーターのロジックは同じです。つまり、フローが新しい値を出力するたびに、現在のコレクターが処理されていない場合、実行がキャンセルされ、新しく起動されます値、つまり:
val startTime = System.currentTimeMillis() // remember the start time
(1..3).asFlow().onEach { delay(100) } // a number every 100 ms
.flatMapLatest { requestFlow(it) }
.collect { value -> // collect and print
println("$value at ${System.currentTimeMillis() - startTime} ms from start")
}
flatMapLatestオペレーターの動作原理は、次のログに示されています。
1:最初の開始から142ミリ秒
2:最初の開始から322ミリ秒
3:最初の開始から425ミリ秒
3:2番目の開始から931ミリ秒
フローの例外
エミッターエミッターまたはオペレーターのコードが例外をスローすると、フローも例外で完了します。これらの例外に対処する方法はいくつかあります。
コレクターの例外をキャッチするには、try-catchを使用します
fun foo(): Flow<Int> = flow {
for (i in 1..3) {
println("Emitting $i")
emit(i) // emit next value
}
}
fun main() = runBlocking<Unit> {
try {
foo().collect { value ->
println(value)
check(value <= 1) { "Collected $value" }
}
} catch (e: Throwable) {
println("Caught $e")
}
}
コレクターは、例外が発生するとフロー処理を停止し、例外後のログ出力は次のようにキャプチャされます。
放出1
1
放出2
2
キャッチjava.lang.IllegalStateException:収集2
流れの異常を捉えることができます
try-catchを使用すると、コレクターによってスローされた例外をキャッチすることに加えて、中間オペレーターと端末オペレーターによって生成された例外をキャプチャーできます。次のコードは、中間オペレーターで例外を生成します。
fun foo(): Flow<String> =
flow {
for (i in 1..3) {
println("Emitting $i")
emit(i) // emit next value
}
}
.map { value ->
check(value <= 1) { "Crashed on $value" }
"string $value"
}
fun main() = runBlocking<Unit> {
try {
foo().collect { value -> println(value) }
} catch (e: Throwable) {
println("Caught $e")
}
}
例外がキャッチされ、コレクタープロセスが終了します。ログは次のとおりです。
1つの
文字列を出力1を
2を出力
Caught java.lang.IllegalStateException:Crashed on 2
例外の透明性
フローは、卓越した透明性を確保する必要があります。つまり、上記のコードのように、フローエミッションとコレクションロジック全体をtry-catchで単にラップすることはできません。フローエミッションパーツに対して例外処理ロジックを個別に処理する必要があるため、コレクタは処理前に何が起こったかについて気にする必要がなくなります。異常な状態。
これは、中間オペレーターキャッチを使用してフローの排出部分で例外キャプチャー処理を実行します。キャッチブロックでキャプチャーされた例外に応じて、次の処理方法を含むさまざまな処理を実行できます。
- あなたはできるスロー例外が再スローされます
- でき発する異常な発光値をに変換され
- 例外を無視したり、印刷をログに記録したり、他のコード処理ロジックを使用したりできます
例外は、次のように排出値に変換されます。
foo()
.catch { e -> emit("Caught $e") } // emit on exception
.collect { value -> println(value) }
ログは、try-catchを使用してすべてのコードをラップするのと同じです。
1つの
文字列を出力1を
2を出力
Caught java.lang.IllegalStateException:Crashed on 2
catchオペレーターは、フローの例外透過性を保証しますが、catchブロックの上のフローフローに対してのみ有効です。収集プロセスによって生成された例外を処理できません。次のコードは、収集で例外を生成し、例外をスローします。
fun foo(): Flow<Int> = flow {
for (i in 1..3) {
println("Emitting $i")
emit(i)
}
}
fun main() = runBlocking<Unit> {
foo()
.catch { e -> println("Caught $e") } // does not catch downstream exceptions
.collect { value ->
check(value <= 1) { "Collected $value" }
println(value)
}
}
フローのネストされたtry-catch全体を置き換える、つまりフローの例外の透過性を確保し、収集プロセスの例外をキャプチャして処理できるようにするために、onEachなどを介して、処理の前に収集プロセスのロジックをキャッチに転送できます。オペレーターは、キャッチする前にフロー内の非同期シーケンス値を処理します。
foo()
.onEach { value ->
check(value <= 1) { "Collected $value" }
println(value)
}
.catch { e -> println("Caught $e") }
.collect()
ログ出力は上記と同じで、例外がキャッチされます:
放出1
1
放出2
キャッチjava.lang.IllegalStateException:収集2
フロー完了
フローは、正常に終了したとき、または例外が発生したときに操作を実行する必要がある場合があります。try -catch-finallyを使用して操作を追加するか、またはnull可能なThrowable型パラメーターを持つonCompletion演算子を使用して、現在の終わりが正常か異常である、もちろん、これだけのために決定することができonCompletionの演算子の前に流れ、そしてonCompletionのオペレータが例外を捕捉していない、これは、次のコードを下に伝播していきます。
foo()
.onCompletion { println("Done") }
.collect { value -> println(value) }
fun foo(): Flow<Int> = flow {
emit(1)
throw RuntimeException()
}
fun main() = runBlocking<Unit> {
foo()
.onCompletion { cause -> if (cause != null) println("Flow completed exceptionally") }
.catch { cause -> println("Caught exception") }
.collect { value -> println(value) }
}
上記のコードはログを出力します:
1
フローは非常に完了し
、例外をキャッチ
fun foo(): Flow<Int> = (1..3).asFlow()
fun main() = runBlocking<Unit> {
foo()
.onCompletion { cause -> println("Flow completed with $cause") }
.collect { value ->
check(value <= 1) { "Collected $value" }
println(value)
}
}
上記のコードでは、onCompletionの前のプロセスは例外を生成しなかったため、原因はnullであり、収集で生成された例外は引き続きスローされ、ログは次のようになります。
1
フロー
がスレッド「メイン」のnull 例外で完了しましたjava.lang.IllegalStateException:収集されました2
起動フロー
上記のコードでは、* runBlocking {} *ブロック内、つまりコルーチン内でフロー処理フローを実行します。これにより、フローフロー処理によって次のようなコードの実行がブロックされます。
// Imitate a flow of events
fun events(): Flow<Int> = (1..3).asFlow().onEach { delay(100) }
fun main() = runBlocking<Unit> {
events()
.onEach { event -> println("Event: $event") }
.collect() // <--- Collecting the flow waits
println("Done")
}
ログを出力します:
イベント:1
イベント:2
イベント:3
完了
コレクション後のコードを同時に実行したい場合は、launchIn演算子を使用して、フローのフローを新しいコルーチンに入れ、後続のコードを次のようにすぐに実行できるようにします。
fun main() = runBlocking<Unit> {
events()
.onEach { event -> println("Event: $event") }
.launchIn(this) // <--- Launching the flow in a separate coroutine
println("Done")
}
ログを出力します:
完了
イベント:1
イベント:2
イベント:3
launchInオペレーターによって渡されるパラメーターは、runBlockingコルーチンビルダーによって作成されたCoroutineScopeなど、CoroutineScopeを宣言するオブジェクトである必要があります。通常、このCoroutineScopeは、ライフサイクルが終了したときに、viewModelScope、lifecycleScopeなどの宣言期間が限定されたオブジェクトです。この時点で、その内部フロー処理も終了します。ここで、onEach {…} .launchIn(スコープ)はaddEventListenerとして機能します(コードを使用して、それに応じて着信イベントを処理し、さらに作業を続けます)。 。
launchInオペレーターは同時にジョブを返すため、cancelを使用してフローの収集コルーチンをキャンセルしたり、joinを使用してジョブを実行したりすることもできます。
フローとリアクティブストリーム
フローストリームの設計は、RxJavaなどのReactive Streamsから着想を得ていますが、フローの主な目的は、可能な限り単純な設計を採用し、Kotlin、フレンドリーな中断機能のサポートを使用し、構造化された並行性を守ることです。
他のReactive Streams(RxJavaなど)とは異なりますが、フロー自体もReactive Streamsであるため、Kotlinが提供する変換ライブラリを使用して、KotlinコルーチンとAndroidの使用概要など、2つの間で直接変換できます(3つのkotlinx-coroutines-rx2で説明されているように、コールバックとRxJava呼び出しを中断して関数を中断します)。
参照:
https
://kotlinlang.org/docs/reference/coroutines/flow.html#suspending-functions Androidでの非同期開発:RxJavaとVs. コトリンフロー