Dmytro Danylyk
08/28/2018, 8:37 AMgildor
08/28/2018, 8:42 AMDmytro Danylyk
08/28/2018, 8:46 AMgildor
08/28/2018, 8:46 AMVsevolod Tolstopyatov [JB]
08/28/2018, 8:46 AMUse-case:
I want to launch several coroutines and I want to make sure they will be executed in order.This is what actor does 🙂 https://github.com/Kotlin/kotlinx.coroutines/pull/485
gildor
08/28/2018, 8:46 AMDmytro Danylyk
08/28/2018, 8:49 AMgildor
08/28/2018, 8:49 AMdelay
does in this example. If you replace delay with sleep to emulate long blocking job, everything will work as ThreadPool with blocking operationsDmytro Danylyk
08/28/2018, 8:56 AMgildor
08/28/2018, 8:56 AMDmytro Danylyk
08/28/2018, 8:57 AMgildor
08/28/2018, 8:59 AMdekans
08/28/2018, 11:08 AMjob.join()
for some dispatched work. Otherway, you'll still have works launched in //nemi
08/28/2018, 11:55 AMbdawg.io
08/28/2018, 8:33 PMval processOne = launch(dispatcher) {
println("first start")
delay(2000)
println("first end")
}
launch(dispatcher) {
//this process depends on the first being complete
processOne.join()
println("second start")
delay(100)
println('second end")
}
OR without the 2nd job directly knowing about the first val processOne = launch(dispatcher) {
println("first start")
delay(2000)
println("first end")
}
// process two cannot start until process one is finished
val processTwo = launch(dispatcher, start = CoroutineStart.LAZY) {
println("second start")
delay(100)
println('second end")
}
processOne.join()
processTwo.join()
OR using an actor to manage the joins val jobManager = actor<Job>(capacity = Channel.UNLIMITED) {
channel.consumeEach { it.join() }
}
launch(dispatcher, start = CoroutineStart.LAZY) {
println("first start")
delay(2000)
println("first end")
}.also { jobManager.send(it) }
launch(dispatcher, start = CoroutineStart.LAZY) {
println("second start")
delay(100)
println('second end")
}.also { jobManager.send(it) }
nemi
08/29/2018, 12:09 AMval runnable1 = Runnable {
println("launching first")
Thread.sleep(1000)
println("finishing first")
}
val runnable2 = Runnable {
println("second")
Thread.sleep(250)
println("second finished")
}
with(Executors.newSingleThreadExecutor()) {
submit(runnable1)
submit(runnable2)
shutdown()
awaitTermination(2000, TimeUnit.MILLISECONDS)
}
The two runnables would be executed sequentially, and the two executions would not be interleaved with one another. The output would be:
“launching first”
“finishing first”
“launching second”
“finishing second”
However if you were to rewrite this naively with coroutines
val dispathcher = newSingleThreadContext("foo")
val job1 = launch(context = dispathcher) {
println("launching first")
delay(1000)
println("finishing first")
}
val job2 = launch(context = dispathcher) {
println("launching second")
delay(250)
println("finishing second")
}
job1.join()
job2.join()
You’d be in for a surprise, as the output will be:
“launching first”
“launching second”
“finishing second”
“finishing first”
And that’s because kotlin coroutines don’t quite behave as threads. The two coroutines are dispatched on a single thread, but interleaved due to the suspension points.
One of the power of coroutines is that it is utilizing threads much more efficiently (done properly). You could executed thousands of jobs “concurrently” without exhausting resources.
What you are mentioning about bad design is a valid point. You might need to have proper external coordination or one job needs to know that that it has to wait for another to finish in order to execute.
How you implement that depends on your use case and requirements.
However the question arose if this can be done with a dispatcher in a way that the “top level” coroutines dispatched are executed sequentially not interleaved, but the coroutine internally is allowed to do things concurrently.
Using this approach is prone to a lot of errors and may most probably be bad design as you are not managing state at all.bdawg.io
08/29/2018, 12:25 AMstart
parameter and the available start = CoroutineStart.LAZY
that I used above, you would indeed be scratching your head how to perform such a task. Perhaps an option to my actor example could be to provide an extension function to manage that for you fun SendChannel<Job>.launchNext(context: CoroutineContext, block: suspend CoroutineScope.() -> Unit) = launch(Unconfined) {
send(launch(context, start = CoroutineStart.LAZY, block = block, parent = coroutineContext[Job]))
}
val jobManager = actor<Job>(capacity = Channel.UNLIMITED) {
channel.consumeEach { it.join() }
}
val dispatcher = newSingleThreadContext("foo")
jobManager.launchNext(dispatcher) {
println("first start")
delay(2000)
println("first end")
}
jobManager.launchNext(dispatcher) {
println("second start")
delay(100)
println("second end")
}