Stephan Schroeder
05/19/2020, 8:25 AMrunBlocking
an expensive operation? The docs seem to suggest that it’s a good idea to use it once in the main-method and than stay in suspend-country for the rest of the codebase. But how about sprinkling in a bit of concurrent computation here and there with snippets like this:
val (a, b) = runBlocking(Dispatchers.Default) {
val aDeferred = async {getA()}
val aDeferred = async {getB()}
return@runBlocking aDeferred.await() to bDeferred.await()
}
Doable or bad idea?octylFractal
05/19/2020, 8:50 AMStephan Schroeder
05/19/2020, 9:41 AMgildor
05/19/2020, 9:54 AMKroppeb
05/19/2020, 10:32 AMgildor
05/19/2020, 11:36 AMasync(IO) { getA() }
Ugi
05/19/2020, 5:40 PMapparently, if those a and b are proper suspend functions they will run in parallelI don't think
getA()
or getB()
being suspend function affects them executing in parallel in this case. As far as I understand it, it will depend on the dispatcher.gildor
05/20/2020, 2:17 AMoctylFractal
05/20/2020, 2:19 AMDispatchers.Default
, they would run concurrently but not in parallel, because there is only one thread for runBlocking
gildor
05/20/2020, 2:20 AMoctylFractal
05/20/2020, 2:24 AMdelay
does not represent, no code is executed while the delay is happening (i.e., no CPU usage). using delay
represents a concurrent process.
take for example Javascript, nothing is ever done in parallel with javascript, it is all one single-threaded event loop. that is what I mean by runBlocking
(with no specified dispatcher) being concurrent but not parallelgildor
05/20/2020, 2:24 AMsuspend fun getA() = IO { File("A").readBytes() }
suspend fun getB() = IO { File("B").readBytes() }
octylFractal
05/20/2020, 2:27 AMgildor
05/20/2020, 2:27 AMoctylFractal
05/20/2020, 2:28 AMgildor
05/20/2020, 2:28 AMoctylFractal
05/20/2020, 2:32 AMfun main() {
runBlocking {
val a = async {
println("Computing [A]...")
// be blocking
Thread.sleep(1000)
println("Finish [A]!")
100
}
val b = async {
println("Computing [B]...")
// be blocking
Thread.sleep(1000)
println("Finish [B]!")
200
}
println("Technically concurrent! I can execute with those queued up.")
println(a.await() + b.await())
}
}
both computations are not done in parallel, though they are both done concurrently, i.e. it does not stop you from setting up the code to execute later
obviously, just using Dispatchers.Default
here is enough to alleviate the problemgildor
05/20/2020, 2:33 AMoctylFractal
05/20/2020, 2:33 AMgildor
05/20/2020, 2:33 AMright, but all code at some level is blocking, it’s just for how longAre we talking about absolutes or about practical usage?
runInterruptible(IO)
, so usually it’s better to extract them to own suspend function.
So it makes usage of dispatcher for runBlocking unnecessaryoctylFractal
05/20/2020, 2:54 AM872ms for event loop
346ms for Default dispatcher
gildor
05/20/2020, 2:56 AMoctylFractal
05/20/2020, 2:56 AMgildor
05/20/2020, 2:56 AMoctylFractal
05/20/2020, 2:57 AMgildor
05/20/2020, 2:57 AMoctylFractal
05/20/2020, 2:57 AMgildor
05/20/2020, 2:57 AMoctylFractal
05/20/2020, 2:57 AMKroppeb
05/20/2020, 7:22 AMso you should wrap this chunk to Dispatcher.DefaultI did not know that was best practice, switching dispatcher close to the expensive code itself.
octylFractal
05/20/2020, 7:23 AMgildor
05/20/2020, 7:23 AMUgi
05/20/2020, 7:37 AMbasically, I just want people to be aware of the fact that the event loop does not run anything in parallel -- if you delegate to another dispatcher, then it can await the results from said dispatcher concurrently but the event loop itself is not parallel-capableThis is the gist of it. I think it's also important to be aware of what your default dispatcher is especially in multi platform environment, because on JVM it's backed by a thread pool, so you will get parallel execution, but on JS (always single-thread) and Native (uses SingleThreadDispatcher at least in 1.3.5-native-mt-1.4M1) you will get concurrent but not parallel execution.
rbares
05/20/2020, 3:23 PMkotlinx-coroutines-android
you will find that Dispatchers.Main
is backed by a Handler
using Looper.getMainLooper()
, which is another example of a single-threaded event loop. This same logic also applies to dispatchers based on a single-threaded Executor. The lack of parallel execution in these cases is one of the reasons you can use thread confinement to safely mutate shared state without need for other synchronization mechanisms.