https://kotlinlang.org logo
Title
s

Sam

11/24/2018, 5:04 PM
Does runBlocking's block thread on suspend also apply to child coroutines?
d

Dominaezzz

11/24/2018, 5:16 PM
You want
runBlocking
to block the calling thread until all the `launch`ed child coroutines finish?
s

Sam

11/24/2018, 5:44 PM
It already does that. My question is to guarantee the sequence of child
runBlocking {
        launch {
            delay( 200 )
            println( "first launch done" )
        }

        launch {
            delay( 100 )
            println( "second launch done" )
        }
    }
d

Dominaezzz

11/24/2018, 5:45 PM
Oh, you want to the first
launch
to finish before the second
launch
?
s

Sam

11/24/2018, 5:45 PM
yes
execute all in a sequence blocking on suspend calls (irrespective of whether they are from same coroutine or child or further nested levels)
b

bdawg.io

11/24/2018, 5:47 PM
You would need to have the second one wait
d

Dominaezzz

11/24/2018, 5:47 PM
Then why are you `launch`ing them? `launch`ing means concurrency.
s

Sam

11/24/2018, 5:48 PM
yes in actual code but lets say in a testing code
b

bdawg.io

11/24/2018, 5:48 PM
In which case, as @Dominaezzz just said, is not concurrency so
launch
is just overhead at this point
s

Sam

11/24/2018, 5:50 PM
or let me rephrase it
repeat( 1000 ) {

        runBlocking {
            launch {
                println( "first launch done" )
            }

            launch {
                println( "second launch done" )
            }
        }

    }
the above launch doesn't have any delay and calling it under runBlocking 1000 or any number of times completes the first launch before the second one
Is that just a coincidence or working as design?
d

Dominaezzz

11/24/2018, 5:51 PM
Coincidence.
It's just very very unlikely for the second launch to finish first in this case.
But there's no documentation officially describing coroutine launch/finish order.
šŸ‘ 1
s

Sam

11/24/2018, 5:54 PM
I thought so too but then i tried replacing runBlocking with a GlobalScope.launch and a sleep, i see the race
repeat( 10 ) {

        GlobalScope.launch {
            launch {
                println( "first launch done" )
            }

            launch {
                println( "second launch done" )
            }
        }

        Thread.sleep (100 )

    }
this time i see the race when i repeat it just 10 times
but i don't see that with runBlocking, thats why i was curious
After looking at this, i'm questioning the coincidence of runBlocking
runBlocking {

        launch {
            var sum  = 0

            repeat( 100_000_0000 ) {
                sum += 1
            }

            println( "first launch sum $sum" )
        }

        launch {
            var sum = 0

            repeat( 10 ) {
                sum += 1
            }

            println( "second launch sum $sum" )
        }
    }
first launch sum 1000000000 second launch sum 10
d

Dominaezzz

11/24/2018, 6:44 PM
Curious.
s

Sam

11/24/2018, 6:49 PM
Yeah, runBlocking for sure seems to influence the order
runBlocking {

        launch {

            val time = measureTimeMillis {

                BigInteger(3500, Random()).nextProbablePrime()

            }

            println( "first launch sum $time" )
        }

        launch {

            println( "second launch sum" )
        }
    }
first launch sum 9087 second launch sum
this is where i got into a rabbit hole when i replaced the lengthy operation in first launch with a suspend call to delay(). In that case, second launch (with no or less delay) completes before the first one
b

bdawg.io

11/24/2018, 7:20 PM
The problem seems to be trying to predict how a concurrency library manages tasks in parallel. runBlocking depends on it's backing dispatcher/event loop. Do you get the same results by using
runBlocking(Dispatchers.Default) { ... }
?
šŸ‘ 1
s

Sam

11/24/2018, 7:23 PM
With Dispatchers.Default, i get expected results
the task with less activity ( with or without usage of delay in both task) completes first
b

bdawg.io

11/24/2018, 7:24 PM
Using
GlobalScope.launch
will give you a different result because it uses Dispatchers.Default/CommonPool to obtain additional threads which allows more of your concurrent jobs to execute in parallel.
s

Sam

11/24/2018, 7:25 PM
Gotcha
b

bdawg.io

11/24/2018, 7:28 PM
So if you use
runBlocking
by itself, it only has the current thread, so your first launch goes first on that single thread until it completes or suspends (which is why your 1 billion sum completes before the second starts), there's no additional threads for the second job to go in parallel
s

Sam

11/24/2018, 7:30 PM
and delay suspends the first launch, causing the second one to execute
Thanks, with that it makes sense
So in other words, runBlocking when used by itself guarantees sequential order of child coroutines as long as none of them invoke any suspend functions
b

bdawg.io

11/24/2018, 7:37 PM
There's no documented guarantee or contract that will be the case, but base on observation of the current implementation that does occur šŸ‘
s

Sam

11/24/2018, 7:38 PM
Got it
Yeah, the nitty gritty is not at all obvious for newcomers
d

Daniel Tam

11/25/2018, 10:16 AM
if you want the first launch to finish first every time then you can wrap it in a coroutineScope
or just
join
on it
p

pankajrai

11/25/2018, 12:15 PM
launch and async are coroutine builder where these coroutines run in parallel. In case you want to run them in sequence than use await method of async and pass LAZY as a constructor parameter to async coroutine builder
Do not use GlobalScope its like building a demon thread for better structured concurrency use some kind of scope like runBlocking is one such which limits the scope to a function call.
runBlocking will block the thread until all it's child execution is done so it's not so good to use runBlocking while building Android apps
b

bdawg.io

11/25/2018, 10:25 PM
I think the point of the OP was to understand the dispatching behavior while inside of a scope launched via
runBlocking