@@ -23,8 +23,11 @@ import kotlinx.coroutines.flow.internal.unsafeFlow as flow
23
23
* This function provides a more efficient shorthand for `channel.consumeEach { value -> emit(value) }`.
24
24
* See [consumeEach][ReceiveChannel.consumeEach].
25
25
*/
26
- @ExperimentalCoroutinesApi
27
- public suspend fun <T > FlowCollector<T>.emitAll (channel : ReceiveChannel <T >) {
26
+ @ExperimentalCoroutinesApi // since version 1.3.0
27
+ public suspend fun <T > FlowCollector<T>.emitAll (channel : ReceiveChannel <T >) =
28
+ emitAllImpl(channel, consume = true )
29
+
30
+ private suspend fun <T > FlowCollector<T>.emitAllImpl (channel : ReceiveChannel <T >, consume : Boolean ) {
28
31
// Manually inlined "consumeEach" implementation that does not use iterator but works via "receiveOrClosed".
29
32
// It has smaller and more efficient spilled state which also allows to implement a manual kludge to
30
33
// fix retention of the last emitted value.
@@ -59,51 +62,78 @@ public suspend fun <T> FlowCollector<T>.emitAll(channel: ReceiveChannel<T>) {
59
62
cause = e
60
63
throw e
61
64
} finally {
62
- channel.cancelConsumed(cause)
65
+ if (consume) channel.cancelConsumed(cause)
63
66
}
64
67
}
65
68
69
+ /* *
70
+ * Represents the given receive channel as a hot flow and [receives][ReceiveChannel.receive] from the channel
71
+ * in fan-out fashion every time this flow is collected. One element will be emitted to one collector only.
72
+ *
73
+ * See also [consumeAsFlow] which ensures that the resulting flow is collected just once.
74
+ *
75
+ * ### Cancellation semantics
76
+ *
77
+ * * Flow collectors are cancelled when the original channel is [closed][SendChannel.close] with an exception.
78
+ * * Flow collectors complete normally when the original channel is [closed][SendChannel.close] normally.
79
+ * * Failure or cancellation of the flow collector does not affect the channel.
80
+ *
81
+ * ### Operator fusion
82
+ *
83
+ * Adjacent applications of [flowOn], [buffer], [conflate], and [produceIn] to the result of `receiveAsFlow` are fused.
84
+ * In particular, [produceIn] returns the original channel.
85
+ * Calls to [flowOn] have generally no effect, unless [buffer] is used to explicitly request buffering.
86
+ */
87
+ @ExperimentalCoroutinesApi // since version 1.4.0
88
+ public fun <T > ReceiveChannel<T>.receiveAsFlow (): Flow <T > = ChannelAsFlow (this , consume = false )
89
+
66
90
/* *
67
91
* Represents the given receive channel as a hot flow and [consumes][ReceiveChannel.consume] the channel
68
92
* on the first collection from this flow. The resulting flow can be collected just once and throws
69
93
* [IllegalStateException] when trying to collect it more than once.
70
94
*
95
+ * See also [receiveAsFlow] which supports multiple collectors of the resulting flow.
96
+ *
71
97
* ### Cancellation semantics
72
98
*
73
- * 1) Flow consumer is cancelled when the original channel is cancelled .
74
- * 2) Flow consumer completes normally when the original channel was closed normally and then fully consumed .
75
- * 3) If the flow consumer fails with an exception, channel is cancelled.
99
+ * * Flow collector is cancelled when the original channel is [closed][SendChannel.close] with an exception .
100
+ * * Flow collector completes normally when the original channel is [ closed][SendChannel.close] normally.
101
+ * * If the flow collector fails with an exception, the source channel is [ cancelled][ReceiveChannel.cancel] .
76
102
*
77
103
* ### Operator fusion
78
104
*
79
105
* Adjacent applications of [flowOn], [buffer], [conflate], and [produceIn] to the result of `consumeAsFlow` are fused.
80
106
* In particular, [produceIn] returns the original channel (but throws [IllegalStateException] on repeated calls).
81
107
* Calls to [flowOn] have generally no effect, unless [buffer] is used to explicitly request buffering.
82
108
*/
83
- @FlowPreview
84
- public fun <T > ReceiveChannel<T>.consumeAsFlow (): Flow <T > = ConsumeAsFlow (this )
109
+ @ExperimentalCoroutinesApi // since version 1.3.0
110
+ public fun <T > ReceiveChannel<T>.consumeAsFlow (): Flow <T > = ChannelAsFlow (this , consume = true )
85
111
86
112
/* *
87
113
* Represents an existing [channel] as [ChannelFlow] implementation.
88
114
* It fuses with subsequent [flowOn] operators, but for the most part ignores the specified context.
89
115
* However, additional [buffer] calls cause a separate buffering channel to be created and that is where
90
116
* the context might play a role, because it is used by the producing coroutine.
91
117
*/
92
- private class ConsumeAsFlow <T >(
118
+ private class ChannelAsFlow <T >(
93
119
private val channel : ReceiveChannel <T >,
120
+ private val consume : Boolean ,
94
121
context : CoroutineContext = EmptyCoroutineContext ,
95
122
capacity : Int = Channel .OPTIONAL_CHANNEL
96
123
) : ChannelFlow<T>(context, capacity) {
97
124
private val consumed = atomic(false )
98
125
99
- private fun markConsumed () =
100
- check(! consumed.getAndSet(true )) { " ReceiveChannel.consumeAsFlow can be collected just once" }
126
+ private fun markConsumed () {
127
+ if (consume) {
128
+ check(! consumed.getAndSet(true )) { " ReceiveChannel.consumeAsFlow can be collected just once" }
129
+ }
130
+ }
101
131
102
132
override fun create (context : CoroutineContext , capacity : Int ): ChannelFlow <T > =
103
- ConsumeAsFlow (channel, context, capacity)
133
+ ChannelAsFlow (channel, consume , context, capacity)
104
134
105
135
override suspend fun collectTo (scope : ProducerScope <T >) =
106
- SendingCollector (scope).emitAll (channel) // use efficient channel receiving code from emitAll
136
+ SendingCollector (scope).emitAllImpl (channel, consume ) // use efficient channel receiving code from emitAll
107
137
108
138
override fun broadcastImpl (scope : CoroutineScope , start : CoroutineStart ): BroadcastChannel <T > {
109
139
markConsumed() // fail fast on repeated attempt to collect it
@@ -121,7 +151,7 @@ private class ConsumeAsFlow<T>(
121
151
override suspend fun collect (collector : FlowCollector <T >) {
122
152
if (capacity == Channel .OPTIONAL_CHANNEL ) {
123
153
markConsumed()
124
- collector.emitAll (channel) // direct
154
+ collector.emitAllImpl (channel, consume ) // direct
125
155
} else {
126
156
super .collect(collector) // extra buffering channel, produceImpl will mark it as consumed
127
157
}
0 commit comments