Hi, Can some explain to me why: ```runBlocking { ...
# coroutines
g
Hi, Can some explain to me why:
Copy code
runBlocking {
        launch {
            println("a1")
            runBlocking { delay(100) }
            println("b1")
        }

        launch {
            println("a2")
            runBlocking { delay(300) }
            println("b2")
        }
    }
displays a1 a2 b2 b1 - instead of a1 a2 b1 b2
🤔 1
l
I think this is due to the way the scheduling will work given that everything is executing on the same thread.
runBlocking
will schedule a coroutine using
CoroutineStart.DEFAULT
which is effectively "immediately" - meaning the coroutine scheduled last will execute first.
u
depends on your platform. I would guess, that everything is running on a single thread which is blocked by the first
runBlocking { delay
that happens to start. if this would be your `runBlocking { delay(300) }`then it blocks the second, shorter deelay from even starting
l
It looks like it would be racy, I assume that you're not using
runBlocking
inside a suspend function in real code?
A blocking call in the coroutine such as
Thread.sleep
yields the expected results as there's no additional scheduling at play.
g
The issue is that I do not control the code between a and b - it's provided by users (I'm writing a framework)
u
can’t your framework provide a callback based api or return a completable future instead of blocking
g
Thx, it provides some good insights. Coroutines can be tricky sometimes
u
👍 1
Maybe you can use
async
and
Deferred.asCompletableFuture
to return a completable future instead of blocking: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutine[…]ture/kotlinx.coroutines.-deferred/as-completable-future.html
m
because, it’s only one thread and delay will ‘yield’ it to the second launch
in case it’s not only one thread, that will produce the same result
o
Why wouldn't it?
u
You guys lost me. What is "because it's only one thread"? And what "wouldn't it"?
o
I think the answer to the original question hasn't anything to do with threads... The output comes because of there are two coroutines being launched. a1 comes first, but before b1 comes it has to wait 100 ms, so a2 comes immediately after. For b2 then it has to wait 300ms, so in the meanwhile, b1 gets logged. Then after comes b2.
runBlocking only blocks when the direct code under suspends, but it never suspends, two coroutines are just being launched
u
@Orhan Tozan But run blocking blocks the thread. So a single threaded dispatcher can not dispatch another coroutine while one is blocking it's only thread
m
It doesn’t block, it waits for the work to finish in the same scope. launch blocks are independent, runblocking inside the first launch won’t influence the second. Even if there is only one thread
u
@Orhan Tozan b1 should come first, then b2 as you say. But if you read the original question, this is not what the poster experienced.
o
My answer is explaining a1 a2 b1 b2
u
@myanmarking this is plain wrong. And if it where so, I'd be interested, why you expect b2 before b1
m
nvm. i read it wrong 😛
The normal execution, from the code you provided, is: a1/a2 then b1 then b2
u
@Orhan Tozan @myanmarking so we all agree 🤣
m
are you sure the test as it is produces a1 a2 b2 b1
1s let me check
the inner runBlocking seems to be the problem. Without them, it’s the expected resukt
u
yes! the first inner runBlocking keeps the only thread of the dispatcher blocked from doing anything else.
runBlocking { delay(300) }
is basically like Thread.sleep(300)
And keeping the inner runBlocking but dispatching on a multi threaded dispatcher things work as expected as well. (See conversation above)
m
how does it explain the output being a1 a2 b1 b2
u
It’s actualy much more interesting then I thought 🙂 multiple runBlocking on the same thread share an event loop. So they do not block the thread.
It looks like then it waits for the shared event loop to finish all coroutines. I’ll double check that.
m
I think you are confused with the work ‘blocking’. RunBlocking blocks no threads
runblocking must have a ‘yield’ somewhere. Your output is expected
u
well, it obviously yields when it’s block terminates. But why? and what is it waiting for? In the original example, during the time 100ms..300ms all coroutines are suspended and for some reason the first runBlocking does not return yet to print
b1
m
not when block terminates. How do you explain the output of my sample, please tell me. it blocks no threads
u
yes, that’s what I wrote above. all runBlocking use thee same event queue. so you are right, the 2 coroutines scheduled by the 2 runBlocking actually execute concurrently. But in the original example you see that b1 is not executed at 100ms but waits until the second runBlocking also terminates. That’s why b1 prints only after b2 and that is unexpected
m
Well, the example is bad. If you have a single thread, and you want to bridge async non-coroutine code, why would you use two launches? It’s useless since you only have one thread and your code does not suspend
u
What an insight 😉 It’s true, but does not explain anything 😂 And the code does suspend in
delay
m
yes, the code does suspend in delay but the usage is wrong. You don’t use runBlock inside a suspend function afaik. Even lint flags you that. It defeats the purpose of suspending
👍 1
g
@myanmarking I’m aware of that. As said above, the code between println is actually a method.invoke in my code. And what it does is not under my control
u
@Gilles Barbier to make your tests / demo independent of the somewhat surprising
runBlocking
behaviour, replace
runBlocking {delay(100)}
in your tests with
Thread.sleep(100)
m
method.invoke, is sync or async ? Edit: Actually, i do think it does not matter. You normally do not use runBlocking this way, it’s the other way around.
g
@uli then the output is a1 b1 a2 b2 (which is not the expected a1 a2 b1 b2, and different that a1 a2 b2 b1 obtained with runBlocking )
u
But that’s what you will get when calling a ‘real’ blocking method from some library. On a single threaded dispatcher you get a1 b1 a2 b2. On IO/Default you get a1 a2 b1 b2. The two
launch
start two concurrent coroutines. Because they don’t suspend though, they are not interleaved on a single threaded dispatcher.
On a single thread a coroutine can only be dispatched when another one suspends (gives up the thread)