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.
Related
Main function:
fun main() = runBlocking {
val channel = Channel<Int>()
foo(channel)
for (y in channel) println(y)
println("Done!")
}
This works:
fun CoroutineScope.foo(channel: Channel<Int>) {
launch {
for (x in 1..5) channel.send(x * x)
channel.close()
}
}
This does not work (it blocks on channel send):
suspend fun foo(channel: Channel<Int>) = coroutineScope {
launch {
for (x in 1..5) channel.send(x * x)
channel.close()
}
}
Why?
As stated in the documentation of coroutineScope:
This function returns as soon as the given block and all its children coroutines are completed
So, writing coroutineScope { launch { something() }} is equivalent to just something(), because it waits for the launch's completion. This, in turn, means that your second version of foo returns only when the loop is completed and the channel closed.
On the other hand, using just launch launches an async task, so the code after foo() can work concurrently with it.
Actually, if you inline foo() in both cases, you will realise that the launch-ed coroutine is delimited by runBlocking's scope in the first case, and by coroutineScope's scope in the second case.
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.
I'm learning concurrency in Kotlin, coming from C#/JavaScript background, and I can't help comparing some concepts.
In C# and JavaScript, technically we can rewrite an async function as a regular non-async version doing the same thing, using Task.ContinueWith or Promise.then etc.
The caller of the function wouldn't even notice the difference (I ranted about it in a blog post).
Is something like that possible for a suspend function in Kotlin (i.e., without changing the calling code)? I don't think it is, but I thought I'd still ask.
The closest thing I could come up with is below (Kotlin playground link), I still have to call .await():
import kotlinx.coroutines.*
suspend fun suspendableDelay(ms: Long): Long {
delay(ms);
return ms;
}
fun regularDelay(ms: Long): Deferred<Long> {
val d = CompletableDeferred<Long>()
GlobalScope.async { delay(ms); d.complete(ms) }
return d;
}
suspend fun test(ms: Long): Long {
delay(ms);
return ms;
}
fun main() {
val r1 = runBlocking { suspendableDelay(250) }
println("suspendableDelay ended: $r1");
val r2 = runBlocking { regularDelay(500).await() }
println("regularDelay ended: $r2");
}
https://pl.kotl.in/_AmzanwcB
If you're on JVM 8 or higher, you can make a function that calls the suspend function in an async job and returns a CompletableFuture, which can be used to get your result with a callback (thenApplyAsync()) or synchronously (get()).
val scope = CoroutineScope(SupervisorJob())
suspend fun foo(): Int {
delay(500)
return Random.nextInt(10)
}
fun fooAsync(): CompletableFuture<Int> = scope.async { foo() }.asCompletableFuture()
fun main() {
fooAsync()
.thenApplyAsync { println(it) }
Thread.sleep(1000)
}
The above requires the kotlinx-coroutines-jdk8 library.
I don't know of a solution that works across multiple platforms.
This can only work if you change your suspending function to a non-suspending blocking function, for example
private fun method(){
GlobalScope.launch {
val value = getInt()
}
}
// Calling coroutine can be suspended and resumed when result is ready
private suspend fun getInt(): Int{
delay(2000) // or some suspending IO call
return 5;
}
// Calling coroutine can't be suspended, it will have to wait (block)
private fun getInt(): Int{
Thread.sleep(2000) // some blocking IO
return 5;
}
Here you can simply use the non-suspending version, without any change on the caller.
But the issue here is that without suspend modifier the function becomes blocking and as such it can not cause the coroutine to suspend, basically throwing away the advantage of using coroutiens.
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
I have created an abstract Event class which is used to create events in Kotlin. Now I would like to use Coroutines to call each subscriber asynchronously.
abstract class Event<T> {
private var handlers = listOf<(T) -> Unit>()
infix fun on(handler: (T) -> Unit) {
handlers += handler
println(handlers.count())
}
fun emit(event: T) =
runBlocking {
handlers.forEach { subscriber ->
GlobalScope.launch {
subscriber(event)
}
}
}
}
And a concrete class that can be used to create event listeners and event publishers
class AsyncEventTest {
companion object : Event<AsyncEventTest>()
fun emit() = emit(this)
}
The issue is that when I run the following code I can see it creates all the listeners, but not even half of them are executed.
fun main(args: Array<String>) {
val random = Random(1000)
runBlocking {
// Create a 1000 event listeners with a random delay of 0 - 1000 ms
for (i in 1..1000)
AsyncEventTest on {
GlobalScope.launch {
delay(random.nextLong())
println(i)
}
}
}
println("================")
runBlocking {
// Trigger the event
AsyncEventTest().emit()
}
}
What am I missing here?
Update
When I remove delay(random.nextLong(), all handlers are executed. This is weird, since I'm trying to simulate different response times from the handlers that way and I think a handler should always execute or throw an exception.
You are running the event listeners with GlobalScope.launch() that does not interact with the surrounding runBlocking() scope. Means runBlocking() returns before all launched coroutines are finished. That is the reason you don't see the output.
BTW: your usage of coroutines and runBlocking is not recommended
You should add suspend to the emit() function. The same is true for the handler parameter - make it suspendable.