Basic question: Am I able to create a `Channel`, l...
# coroutines
r
Basic question: Am I able to create a
Channel
, launch a coroutine in
CoroutineContext
A to send some data through the channel, and then launch a coroutine in
CoroutineContext
B in order to receive that data? I threw together a basic example and it looks like you must send/receive to/from a channel in the same
CoroutineContext
. If so, why is this the case?
k
Am I able to
Yes
it looks like you must send/receive to/from a channel in the same
CoroutineContext
No. It doesn’t have to be the same coroutine context. You can go through the documentation on channels. It shows several examples of this https://kotlinlang.org/docs/reference/coroutines/channels.html
r
Thanks for clarifying. So I’ve put together a really dumb example:
Copy code
fun main() {
    runBlocking {
        produce()
        consume()
    }
}

val channel = Channel<String>()

suspend fun produce() {
    coroutineScope {
        launch {
            println("Producing")
            channel.send("Hello world")
            println("Done producing")
        }
    }
}

suspend fun consume() {
    coroutineScope {
        launch {
            println("Consuming")
            val value = channel.receive()
            println(value)
        }
    }
}
My understanding is that the
coroutineScope
builder creates a new scope that inherits the context of its parent, which in this case is blocking. I don’t understand why
channel.send
is blocks the thread in this case though.
Done producing
is never printed.
If I reorganize my example to the following it works as expected
Copy code
fun main() {
    runBlocking {
        produce()
        consume()
    }
}

val channel = Channel<String>()

fun CoroutineScope.produce() {
    launch {
        println("Producing")
        channel.send("Hello world")
        println("Done producing")
    }
}

fun CoroutineScope.consume() {
    launch {
        println("Consuming")
        val value = channel.receive()
        println(value)
    }
}
s
Because
coroutineScope { ... }
only resumes/returns after all its child coroutines have finished. In your example, the
courtineScope
calls `launch`creating a child-coroutine that never finishes. It never finishes, because this child-coroutine calls
channeld.send
on a RENDEZVOUS channel. the
send
call will never resume/return.
The
channel.send
never resumes because the
channel.receive
is never called.
The
channel.receive
is never called because the
consume
function is never called. This is because
produce
never resumes/returns in your first code-sample.
k
In your first example.
produce
and
consume
are 2 suspending methods, and they both follow structured concurrency which is good. But this means they will execute sequencially.
consume
will only get called after
produce
has completed. But this will never happen because produce will get suspended waiting for a consumer that will never come The second example is indeed the right way to go about this, since each method is executed concurrently, so produce and consume are able to communicate with each other If you really want 1 to work though, you could set a buffer value for the channel, but that doesn’t really scale if you want implement a proper channel based setup
r
Gotcha, thank you @streetsofboston and @kingsley I’m really more interested in understanding the underlying scoping mechanic here. I’ve typically gone with the approach in the 2nd example, but I couldn’t explain to my coworkers why example 1 didn’t work.
Because
coroutineScope { ... }
only resumes/returns after all its child coroutines have finished.
being the key reason 👍
b
I would highly recommend watching Roman's talk at HydraConf about Structured Concurrency which goes into the design paradigm and rationale for
coroutineScope
to best understand how you would pick one over the other

https://youtu.be/Mj5P47F6nJg

👍 1