Will the collect of the Flow block to execute? - kotlin

I run the Code A and get the Result A. In my mind, it should be the Result B.
It seems that flow.collect { value -> println(value) } block to execute.
Will the collect of the Flow block to execute ?
Code A
fun simple(): Flow<Int> = flow {
println("Flow started")
for (i in 1..3) {
delay(300)
emit(i)
}
}
fun main() = runBlocking<Unit> {
println("Calling simple function...")
val flow = simple()
println("Calling collect...")
flow.collect { value -> println(value) } //Block?
println("Calling collect again...")
}
Result A
Calling simple function...
Calling collect...
Flow started
1
2
3
Calling collect again...
Result B
Calling simple function...
Calling collect...
Flow started
Calling collect again...
1
2
3
BTW, I run Code 1 and get the result 1 as I expected.
Code 1
fun simple(): Flow<Int> = flow {
for (i in 1..3) {
delay(100)
emit(i)
}
}
fun main() = runBlocking<Unit> {
launch {
for (k in 1..3) {
println("I'm not blocked $k")
delay(100)
}
}
simple().collect { value -> println(value) }
}
Result 1
I'm not blocked 1
1
I'm not blocked 2
2
I'm not blocked 3
3

Suspend functions do not block, but they are synchronous, meaning the execution of the code in the coroutine waits for the suspend function to return before continuing. The difference between suspend function calls and blocking function calls is that the thread is released to be used for other tasks while the coroutine is waiting for the suspend function to return.
collect is a suspend function that internally calls its lambda repeatedly and synchronously (suspending, not blocking) and doesn't return until the Flow is completed.
launch is an asynchronous function that starts a coroutine. It returns immediately without waiting for its coroutine to complete, which is why Code 1 behaved as you expected.

Related

Why can I cancel a Flow without either invoking yield or determining isActive() identification in Kotlin?

I have read the article.
There are two approaches to making computation code cancellable. The first one is to periodically invoke a suspending function that checks for cancellation. There is a yield function that is a good choice for that purpose. The other one is to explicitly check the cancellation status.
I know Flow is suspending functions.
I run Code B , and get Result B as I expected.
I think I can't making computation Code A cancellable, but in fact I can click "Stop" button to cancel Flow after I click "Start" button to emit Flow, why?
Code A
class HandleMeter: ViewModel() {
var currentInfo by mutableStateOf(2.0)
private var myJob: Job?=null
private fun soundDbFlow() = flow {
while (true) {
val data = (0..1000).random().toDouble()
emit(data)
}
}
fun calCurrentAsynNew() {
myJob?.cancel()
myJob = viewModelScope.launch(Dispatchers.IO) {
soundDbFlow().collect {currentInfo=it }
}
}
fun cancelJob(){
myJob?.cancel()
}
}
#Composable
fun Greeting(handleMeter: HandleMeter) {
var currentInfo = handleMeter.currentInfo
Column(
modifier = Modifier.fillMaxSize(),
) {
Text(text = "Current ${currentInfo}")
Button(
onClick = { handleMeter.calCurrentAsynNew() }
) {
Text("Start")
}
Button(
onClick = { handleMeter.cancelJob() }
) {
Text("Stop")
}
}
}
Code B
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = launch(Dispatchers.IO) {
cal()
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancelAndJoin()
println("main: Now I can quit.")
}
suspend fun cal() {
val startTime = System.currentTimeMillis()
var nextPrintTime = startTime
var i = 0
while (i < 5) {
if ( System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
Result B
job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
job: I'm sleeping 3 ...
job: I'm sleeping 4 ...
main: Now I can quit.
Add Content:
To Tenfour04: Thanks!
If the following content you said is true. I think Code C can be canceled when system finish the operation doBigBlockingCalculation() at one time, right? Why do I need Code D?
Since emit() is a suspend function, your Flow is able to interrupt and end the coroutine the next time the emit() function is called in that while loop.
Code C
private fun complicatedFlow() = flow {
while (true) {
val data = (0..1_000_000).doBigBlockingCalculation()
emit(data)
}
}.flowOn(Dispatchers.Default) // since the calculation is blocking
Code D
private fun complicatedFlow() = flow {
while (true) {
val data = (0..1_000_000)
.chunked(100_000)
.flatMap {
it.doBigBlockingCalculation().also { yield() }
}
emit(data)
}
}.flowOn(Dispatchers.Default) // since the calculation is blocking
A Flow on its own is cold. Its a wrapper around some suspend functions that will run when collect() or some other terminal suspending function is called on the Flow.
In your Code A, when the Job is cancelled, it is cancelling the coroutine that called collect on the Flow. collect is a suspend function, so that cancellation will propagate down to the function you defined inside soundDbFlow(). Since emit() is a suspend function, your Flow is able to interrupt and end the coroutine the next time the emit() function is called in that while loop.
Here's an example for how you could use this knowledge:
Suppose your function had to do a very long calculation like this:
private fun complicatedFlow() = flow {
while (true) {
val data = (0..1_000_000).doBigBlockingCalculation()
emit(data)
}
}.flowOn(Dispatchers.Default) // since the calculation is blocking
Now if you tried to cancel this flow, it would work, but since the data line is a very slow operation that is not suspending, the Flow will still complete this very long calculation for no reason, eating up resources for longer than necessary.
To resolve this problem, you could break your calculation up into smaller pieces with yield() calls in between. Then the Flow can be cancelled more promptly.
private fun complicatedFlow() = flow {
while (true) {
val data = (0..1_000_000)
.chunked(100_000)
.flatMap {
it.doBigBlockingCalculation().also { yield() }
}
emit(data)
}
}.flowOn(Dispatchers.Default) // since the calculation is blocking
Not a perfect example. It's kind of wasteful to chunk a big IntRange. An IntRange takes barely any memory, but chunked turns it into Lists containing every value in the range.
It has to do with CoroutineScopes and children of coroutines.
When a parent coroutine is canceled, all its children are canceled as well.
More here:
https://kotlinlang.org/docs/coroutine-context-and-dispatchers.html#children-of-a-coroutine

Kotlin async function not run in parallel

I have a kotlin function just as following, And I expected it can wrap a sync IO action into async.
suspend fun <T> runIOAsync(f:suspend () -> T): Deferred<T> = coroutineScope{
async(Dispatchers.IO) {
f()
}
}
Then I have my code in the calling side like
runBlocking {
repeat(5) {
runIOAsync {
println(it)
println(Thread.currentThread())
Thread.sleep(3000)
println("After sleep $it")
}.await()
}
}
But the actual out put is
0
Thread[DefaultDispatcher-worker-1 #coroutine#2,5,main]
After sleep 0
1
Thread[DefaultDispatcher-worker-1 #coroutine#3,5,main]
After sleep 1
2
Thread[DefaultDispatcher-worker-1 #coroutine#4,5,main]
After sleep 2
3
Thread[DefaultDispatcher-worker-1 #coroutine#5,5,main]
After sleep 3
4
Thread[DefaultDispatcher-worker-1 #coroutine#6,5,main]
After sleep 4
Which seems all tasks from my function are executed serially. Any one can please help to give an explanation
Let's put aside runIOAsync for the moment.
You're using await() right after calling async(), which means that you're effectively waiting for the end of your async execution before executing the next one.
Instead, start all tasks first and then await all of them. For instance you can use awaitAll:
runBlocking {
List(5) {
async(Dispatchers.IO) {
println(it)
println(Thread.currentThread())
Thread.sleep(3000)
println("After sleep $it")
}
}.awaitAll()
}
Also, the way you're encapsulating the scope in runIOAsync is wrong, you will be waiting for the end of the async execution even without calling await() (coroutineScope is a suspending function that waits for all its child coroutines before resuming).
Instead, use coroutineScope to define the boundary of your coroutines executions, and you don't even have to await them. Since you don't need to get values from this code, you can also use launch instead of async here:
coroutineScope {
repeat(5) {
launch(Dispatchers.IO) {
println(it)
println(Thread.currentThread())
Thread.sleep(3000)
println("After sleep $it")
}
}
}
Declaring a suspend function returning a Deferred should be a red flag, and is quite confusing from an API standpoint: if you suspend it means you wait, if you return Deferred it means you don't wait (you immediately return a handle to some running computation). A function that does both would be quite weird.
If what you want is to make suspending code from IO-bound code, you can use instead the existing withContext function:
// this suspends until the code inside is done
withContext(Dispatchers.IO) {
// run some blocking IO code
}
However, note that this is independent from defining concurrent code. If you want to run multiple things concurrently, even with suspend functions, you'll need coroutine builders such as async or launch like in the above code.

Coroutines how to wait for the data and then continue process

I'm learning coroutines with kotlin, and I have problem how process can wait until process 1 finished then it continue to process 2, from my sample below I have object Network which access API server using getNews(it's running well and get the data)
I called this getNews from refreshNews using asynch - await, with the purpose it wait for the data then it continue running, but "The program Not Wait", it just running process 2 then process 1 finish, so I cannot capture data from API in refresh news
// process 1 - calling api this running well can get the data see process 2
object Network {
var status : NewsApiStatus = NewsApiStatus.LOADING
private var viewModelJob = Job()
private val coroutineScope = CoroutineScope(viewModelJob + Dispatchers.Main)
fun getNews(filter: String, page: Int =1) : newsData? {
var allNews : newsData? = null
coroutineScope.launch {
RetrofitClient.instance.getAllNews(filter, page).enqueue(object: Callback<newsData>{
override fun onFailure(call: Call<newsData>, t: Throwable) {
status = NewsApiStatus.ERROR
}
override fun onResponse(
call: Call<newsData>,
response: Response<newsData>
) {
status = NewsApiStatus.DONE
var listResult = response.body()
if (listResult != null) {
if (listResult.data.isNotEmpty()) {
allNews = listResult
Timber.tag(TAG).i( "process 1 total allNews = ${allNews!!.data.size}")
}
}
}
})
}
return(allNews)
}
}
// process 2 - calling process 1 with runBlocking
fun refreshNews() = runBlocking{
val newsData = async {
Network.getNews("")
}
Timber.tag(TAG).i("proses 2 ${newsData.await()?.data?.size}")
// here I want newsData to wait until it has data
}
// this main program that call process 2
class NewsListViewModel(application: Application) : AndroidViewModel(application) {
init {
refreshNews()
}
}
launch returns a reference to the started job. You can use it to wait for the job to finish by calling join():
val job = GlobalScope.launch { // launch a new coroutine and keep a reference to its Job
// ...
}
runBlocking {
job.join() // wait until child coroutine completes
}
Currently, your getNews() launches a coroutine and immediately returns. allNews isn't initialised at that point yet.
You need to either call job.join() inside getNews() (would make it blocking), or use async inside getNews() and return its result if you want to keep it asynchronous (you'd need to take the result differently from your http client as you won't be able to initialise the variable declared outside).
It's worth to go through the official coroutine docs:
https://kotlinlang.org/docs/reference/coroutines/basics.html
https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html

Why can't I parallel operation when I use either delay() or yield() in Kotlin?

The Code A, Code B and Code C get the same result Result All.
I think the Code B or Code C should get the result Result MyThink because I have added either delay() or yield().
It seems that flow.collect {...} is a block function.
Code A
fun foo(): Flow<Int> = flow {
println("Flow started")
for (i in 1..3) {
delay(500)
emit(i)
}
}
fun main() = runBlocking<Unit> {
println("Calling foo...")
val flow = foo()
println("Calling collect...")
flow.collect { value ->run {
println(value)
}
}
println("Done")
}
Code B
fun foo(): Flow<Int> = flow {
println("Flow started")
for (i in 1..3) {
delay(500)
emit(i)
}
}
fun main() = runBlocking<Unit> {
println("Calling foo...")
val flow = foo()
println("Calling collect...")
flow.collect { value ->run {
println(value)
delay(200)
}
}
println("Done")
}
Code C
fun foo(): Flow<Int> = flow {
println("Flow started")
for (i in 1..3) {
delay(500)
emit(i)
}
}
fun main() = runBlocking<Unit> {
println("Calling foo...")
val flow = foo()
println("Calling collect...")
flow.collect { value ->run {
println(value)
yield()
}
}
println("Done")
}
Result All
Calling foo...
Calling collect...
Flow started
1
2
3
Done
Result MyThink
Calling foo...
Calling collect...
Flow started
1
Done
2
3
It seems that flow.collect {...} is a block function.
That's not true in a literal sense, but there really is behaviour here that you might phrase as "blocking".
collect is a suspending function, which will return only after it has collected all of the items in the Flow that it was called on. Whenever the Flow suspends (with delay or yield, for example), the collection of the Flow is also suspended. This is all happening in the same coroutine (started by runBlocking in this case) that's suspended together. The Flow yielding values and collect processing them will continue after the suspension is over. Finally, when everything's collected, collect will return, and any code you have after it in that same coroutine will run.
This is consistent with the idea that coroutines are sequential by default, i.e. everything is executed top-to-bottom in your code, in order. If you want concurrent behaviour, you have to explicitly opt into it (for example, by launching new coroutines within the current one, with launch, or async). So what you call "blocking" is really just sequential. The collect function does not work like registering a listener would with many other APIs.
To understand the basic idea behind Flow, and how collecting it works within the same coroutine, I always recommend this talk.
If you want to have similar behavior as in Rx
you can use onEach instead collect with launchIn(this)
flow.onEach {
print(it)
}.launchIn(this)
https://proandroiddev.com/from-rxjava-2-to-kotlin-flow-threading-8618867e1955

What are differents between foo().collect{...} and launch { foo().collect{...} }?

The Code A get the Result A, and the Code B get the Result B .
1: Is the fun foo() coroutines ? why doesn't it add keyword suspend before if it's a coroutines function?
2: In my mind, a coroutines function is fired using launch {...} or aync {...} if fun foo() is a coroutines function, why can the main() fun in Code B invoke it directly?
3: What are differents between Code A and Code B ?
Code A
fun main() = runBlocking<Unit> {
launch {
for (k in 1..3) {
println("I'm not blocked $k")
delay(100)
}
}
launch {
foo().collect { value -> println(value) }
}
}
fun foo(): Flow<Int> = flow {
for (i in 1..3) {
delay(100)
emit(i+5)
}
}
Result A
I'm not blocked 1
I'm not blocked 2
6
I'm not blocked 3
7
8
Code B
fun main() = runBlocking<Unit> {
launch {
for (k in 1..3) {
println("I'm not blocked $k")
delay(100)
}
}
foo().collect { value -> println(value) }
}
fun foo(): Flow<Int> = flow {
for (i in 1..3) {
delay(100)
emit(i+5)
}
}
Result B
I'm not blocked 1
6
I'm not blocked 2
7
I'm not blocked 3
8
The foo function is not a suspending function, so you can call it outside a coroutine scope. This function builds a flow and returns the flow, but the flow does not start.
The flow only starts when you call collect on it, and that is a suspending function, so calling collect must be done in a coroutine scope.
flows are cold, they only start emitting items when you call collect on them, so building the flow is not a suspending function because it's a fast operation.
1: Is the fun foo() coroutines ? why doesn't it add keyword suspend before if it's a coroutines function?
Actually foo() is a regular function that returns a Flow.
2: In my mind, a coroutines function is fired using launch {...} or aync {...} if fun foo() is a coroutines function, why can the main() fun in Code B invoke it directly?
Also using runBlocking{...}, runBlockingTest{...}, produce{...} and actor{...}.
It doesn't matter where you build/create/declare a Flow, what matters is where you call terminal operators (like collect) which are suspending functions and must be called inside a coroutine or inside another suspending function. Btw, you're running all the main method inside a coroutine since you're wrapping it inside a runBlocking block.
3: What are differents between Code A and Code B ?
None. You're just creating another coroutine on Code A which will also run on the main thread.