https://kotlinlang.org logo
Title
g

Gilles Barbier

03/11/2021, 10:20 AM
Hi, Can some explain to me why:
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

lewis

03/11/2021, 10:54 AM
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

uli

03/11/2021, 10:54 AM
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

lewis

03/11/2021, 10:55 AM
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

Gilles Barbier

03/11/2021, 10:58 AM
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

uli

03/11/2021, 10:59 AM
can’t your framework provide a callback based api or return a completable future instead of blocking
g

Gilles Barbier

03/11/2021, 10:59 AM
Thx, it provides some good insights. Coroutines can be tricky sometimes
u

uli

03/11/2021, 11:03 AM
launch(Dispatchers.Default)
wil solve your imediate problem. But is more like a band Aid here: https://play.kotlinlang.org/#eyJ2ZXJzaW9uIjoiMS4zLjQxIiwicGxhdGZvcm0iOiJqYXZhIiwiYXJncy[…]AgICAgcHJpbnRsbihcImIyXCIpXG4gICAgICAgIH1cbiAgICB9XG59In0=
👍 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

myanmarking

03/11/2021, 3:06 PM
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

Orhan Tozan

03/11/2021, 6:40 PM
Why wouldn't it?
u

uli

03/11/2021, 6:49 PM
You guys lost me. What is "because it's only one thread"? And what "wouldn't it"?
o

Orhan Tozan

03/11/2021, 6:52 PM
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

uli

03/11/2021, 6:56 PM
@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

myanmarking

03/11/2021, 6:57 PM
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

uli

03/11/2021, 6:57 PM
@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

Orhan Tozan

03/11/2021, 6:58 PM
My answer is explaining a1 a2 b1 b2
u

uli

03/11/2021, 6:59 PM
@myanmarking this is plain wrong. And if it where so, I'd be interested, why you expect b2 before b1
m

myanmarking

03/11/2021, 6:59 PM
nvm. i read it wrong 😛
The normal execution, from the code you provided, is: a1/a2 then b1 then b2
u

uli

03/11/2021, 7:02 PM
@Orhan Tozan @myanmarking so we all agree 🤣
m

myanmarking

03/11/2021, 7:03 PM
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

uli

03/11/2021, 7:39 PM
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

myanmarking

03/12/2021, 12:53 AM
how does it explain the output being a1 a2 b1 b2
u

uli

03/12/2021, 8:12 AM
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

myanmarking

03/12/2021, 10:09 AM
I think you are confused with the work ‘blocking’. RunBlocking blocks no threads
runblocking must have a ‘yield’ somewhere. Your output is expected
u

uli

03/12/2021, 10:14 AM
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

myanmarking

03/12/2021, 10:15 AM
not when block terminates. How do you explain the output of my sample, please tell me. it blocks no threads
u

uli

03/12/2021, 10:25 AM
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

myanmarking

03/12/2021, 10:32 AM
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

uli

03/12/2021, 10:37 AM
What an insight 😉 It’s true, but does not explain anything 😂 And the code does suspend in
delay
m

myanmarking

03/12/2021, 10:40 AM
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

Gilles Barbier

03/12/2021, 11:33 AM
@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

uli

03/12/2021, 11:36 AM
@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

myanmarking

03/12/2021, 11:38 AM
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

Gilles Barbier

03/12/2021, 4:23 PM
@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

uli

03/12/2021, 4:46 PM
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)