https://kotlinlang.org logo
#coroutines
Title
# coroutines
a

Allan Wang

12/27/2018, 6:07 AM
In this example, running the code will print ‘done’, ‘1 end’, then ‘2 end’, each in a different thread. However, the for loop never seems to run. If I change both
launch
to
launch(<http://Dispatchers.IO|Dispatchers.IO>)
, then the loops each print 3 times. What’s going on here? In production, would I be expected to specify a different context if I want the loops to produce results? Was adding the loops in separate launches not enough?
b

bdawg.io

12/27/2018, 6:10 AM
On a side note, you should use
@UseExperimental(ExperimentalCoroutinesApi::class)
instead of annotating your code with it directly
Because you are using
runBlocking
it only uses the current thread for
launch
(as opposed to
launch(IO)
). So by the time either of the
launch
coroutines start, you have already invoked send 1, 2, 3, AND
close()
. Since the BroadcastChannel is now closed, once the `launch`es get their turn to execute there’s nothing for the
for
loops inside of the `launch`es to actually loop over. Using the
IO
dispatcher gave you more threads for the `launch`es and the
send()
code to run in parallel which is how you were able to loop over the 3 items before the channel was closed
a

Allan Wang

12/27/2018, 6:30 AM
But the output is:
Copy code
Done Thread[main @coroutine#1,5,main]
1 end Thread[main @coroutine#2,5,main]
2 end Thread[main @coroutine#3,5,main]
Doesn’t that mean they are already running in different threads? I thought the difference was that they will only use the threadpool within
Dispatchers.Default
, which is why I added a sleep to give the other threads a chance to finish
Or does launch with an empty context only use the current thread rather than the threadpool in the previous context? I tried
runBlocking(<http://Dispatchers.IO|Dispatchers.IO>)
with the same result
b

bdawg.io

12/27/2018, 4:13 PM
They’re all running on the
main
thread. Each coroutine has it’s own name though.
launch
,
async
, etc inherits the same CoroutineContext in the current scope. In the case of
runBlocking
with an EmptyCoroutineContext, it provides a scope that is bound to the single thread it was launched in. Try doing
c.close()
after your
sleep
. Because your channel has a capacity larger than the
3
items you’re providing, the `send`s are immediately completing all the way through the
close()
. It looks like you’re experiencing races
Just to clarify, if you increased your
sleep
to 10 seconds, you would see
Copy code
Done
<wait for 10 seconds>
1 end
2 end
Is that correct?
a

Allan Wang

12/27/2018, 7:11 PM
With this:
Copy code
@Test
@UseExperimental(ExperimentalCoroutinesApi::class)
fun channel() {
    val c = BroadcastChannel<Int>(100)
    runBlocking(<http://Dispatchers.IO|Dispatchers.IO>) {
        launch(/*<http://Dispatchers.IO|Dispatchers.IO>*/) {
            println("1 start ${Thread.currentThread()}")
            for (i in c.openSubscription()) {
                println("1 $i")
            }
            println("1 end ${Thread.currentThread()}")
        }
        launch(/*<http://Dispatchers.IO|Dispatchers.IO>*/) {
            println("2 start ${Thread.currentThread()}")
            for (i in c.openSubscription()) {
                println("2 $i")
            }
            println("2 end ${Thread.currentThread()}")
        }
        c.send(1)
        c.send(2)
        c.send(3)
        delay(2000)
        println("Done")
        c.close()
    }
}
It always prints both starts, then done, then both ends. Sometimes, it will print the numbers as well. I changed
sleep
to
delay
, and 2 seconds is already plenty, so I’m not sure if it’s a race condition at that point. I always thought that something like
Copy code
launch(context) {
  launch {
    // action
  }
}
was equivalent to
Copy code
launch(context) {
  launch(context) {
    // action
  }
}
but I guess not
b

bdawg.io

12/27/2018, 7:17 PM
When I run your original sample using
runBlocking(<http://Dispatchers.IO|Dispatchers.IO>) {...}
my output is
Copy code
2 1
Done Thread[DefaultDispatcher-worker-1,5,main]
2 2
1 1
2 3
1 2
2 end Thread[DefaultDispatcher-worker-2,5,main]
1 3
1 end Thread[DefaultDispatcher-worker-3,5,main]
That is the case. Jobs launched inside of a scope inherit that scope’s context (which includes things like the dispatcher it should run on)
a

Allan Wang

12/27/2018, 7:22 PM
Does it always output that though? It doesn't seem to be the case for me unless I specify
<http://Dispatchers.IO|Dispatchers.IO>
for all launches. I do expect it to work your way though, given the context isn't changing
I found the issue. I was subscribing to the channel in the launch block which happens asynchronously. If I create the receive channel before launch and then loop through it, everything works as expected. Thanks for your help
2 Views