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

Paulius Ruminas

02/25/2019, 6:37 AM
Hello, why does it not print 1 2 3
Copy code
runBlocking {
            val producer = async {
                produce {
                    send(1)
                    send(2)
                    send(3)
                }
            }

            launch {
                producer.await().consumeEach { println(it) }
            }
        }
g

gildor

02/25/2019, 6:40 AM
Because
async
never returns, just suspend untill all child coroutines are finished (which doesn’t happen in your case). Actual problem that you never call
consumeEach
, because
await()
never return
To fix it, just unwrap produce
What is you real use case for this?
p

Paulius Ruminas

02/25/2019, 6:43 AM
If i unwrap produce then it will not return a channel.
or do you mean remove async?
g

gildor

02/25/2019, 6:43 AM
What do you mean?
yes, remove async
This is how scopes of coroutine builder works, they allow to start child coroutines, but never return until child is finished to prevent coroutine leaking
What you actually trying to do can be achived by launching
produce
without relation to
async
using GlobalScope. but it’s not a recommended way to work with scopes, this is why I’m asking about your use case
p

Paulius Ruminas

02/25/2019, 6:54 AM
Copy code
class A {

    suspend fun startGeneratingData() = coroutineScope {
        produce {
            send(1)
            send(2)
            send(3)
        }
    }
}

class B {

    suspend fun startGeneratingData() = coroutineScope {
        produce {
            send(1)
            send(2)
            send(3)
        }
    }
}

enum class GeneratorType {
    A, B
}

class C(
    private val a: A,
    private val b: B,
    override val coroutineContext: CoroutineContext
): CoroutineScope {

    private val actions = Channel<Action>()

    private sealed class Action {
        data class Start(val type: GeneratorType): Action()
        object Stop: Action()
    }

    init {
        launch {
            val job = coroutineContext + Job()

            actions.consumeEach { action ->
                when (action) {
                    is Action.Start -> launch(job) {
                        when (action.type) {
                            GeneratorType.A -> a.startGeneratingData()
                            GeneratorType.B -> b.startGeneratingData()
                        }
                    }
                    Action.Stop -> job.cancelChildren()
                }
            }
        }
    }

    suspend fun start(type: GeneratorType) {
        actions.send(Action.Start(type))
    }

    suspend fun stop(type: GeneratorType) {
        actions.send(Action.Stop)
    }
}

fun main() = runBlocking {
    val c = C(A(), B(), Dispatchers.Unconfined)

    c.start(GeneratorType.A)
}
I have two types of producers (2 different physical devices that could connected and disconnected)
I want to have a class that would handle both of them
g

gildor

02/25/2019, 6:55 AM
I didn’t check all code, it’s pretty big, but this part is wrong by definition, check documentation of coroutineScope function
Copy code
suspend fun startGeneratingData() = coroutineScope {
        produce {
            send(1)
            send(2)
            send(3)
        }
    }
Instead of using
coroutineScope
which never return if there are any child coroutine in progress, pass CoroutineScope instance to this function or make
startGeneratingData()
extension of coroutine scope
p

Paulius Ruminas

02/25/2019, 6:57 AM
Ok i understand know, coroutineScope will not return untill all children are in progress. Thank you for you help!
g

gildor

02/25/2019, 6:58 AM
Action.Stop -> job.cancelChildren()
as I understand you can replace it with cancel() on this channel
p

Paulius Ruminas

02/25/2019, 7:10 AM
I did it to avoid nullable mutable variable, since one needs to track which producer is active. I did not add it to the example snippet but only one producer can be active so job.cancelChildren happens on any new action in production code.
Copy code
var activeProducer: ReceiveChannel<Int>?
Is there any side effects of doing it with a job instead of a mutable variable?
g

gildor

02/25/2019, 7:14 AM
no side effects, just looks a bit overcomplicated comparing with nullable job
also with nullable job you at least can check is any job started or not
p

Paulius Ruminas

02/25/2019, 8:05 AM
I was thinking why is it named
coroutineScope
? It's a noun so from the name i would think that it is factory method to create a coroutine scope not to launch multiple coroutines with the same job.
launch
is a verb so one assumes that it will run computations but with
coroutineScope
i do not think it is that obvious.
g

gildor

02/25/2019, 8:08 AM
i would think that it is factory method to create a coroutine scope
Yes, and it creates coroutine scope
but I tend to agree, it usual source of misconception of this function, but documentation fortunately is pretty clear
p

Paulius Ruminas

02/25/2019, 8:11 AM
From the call site you can not tell if it creates a coroutine scope (since there is no return CoroutineScope) that is why it is confusing atleast for me.
g

gildor

02/25/2019, 8:13 AM
But shouldn’t create coroutine scope, it provides block with a new coroutine scope
3 Views