I am new to coroutines in Kotlin and wanted to try out a simple example. But the code does not run in parallel as I would have expected. How can I transform the method sleepTwoSec()
so that it can be called inside launch {...}
blocks and executed in parallel? If that matters: I am not interested in any return values (fire and forget) but launchMultipleParallelFuns()
should exit only once all launch
ed calls are done.
The code looks like this:
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.lang.Thread.sleep
import kotlin.time.measureTime
fun main() {
println("main start")
println(measureTime { blockingFunction() })
println("main end")
}
fun blockingFunction() = runBlocking {
println(" start blockingFunction")
launchMultipleParallelFuns()
println(" end blockingFunction")
}
suspend fun launchMultipleParallelFuns() = coroutineScope {
println(" start launchMultipleParallelFuns")
launch { sleepTwoSec(1) }
launch { sleepTwoSec(2) }
launch { sleepTwoSec(3) }
launch { sleepTwoSec(4) }
println(" end launchMultipleParallelFuns")
}
fun sleepTwoSec(n: Int) {
println(" start sleepTwoSec $n")
sleep(2000)
println(" end sleepTwoSec $n")
}
The actual output is:
main start
start blockingFunction
start launchMultipleParallelFuns
end launchMultipleParallelFuns
start sleepTwoSec 1
end sleepTwoSec 1
start sleepTwoSec 2
end sleepTwoSec 2
start sleepTwoSec 3
end sleepTwoSec 3
start sleepTwoSec 4
end sleepTwoSec 4 // from the output it's clear that the calls do not run in parallel
end blockingFunction
8.135047100s // runs for 8 seconds which means no parallelism
main end
The expected output is:
main start
start blockingFunction
start launchMultipleParallelFuns
end launchMultipleParallelFuns // this is expected
start sleepTwoSec 1 // I expect all calls to start at once
start sleepTwoSec 2
start sleepTwoSec 3
start sleepTwoSec 4
end sleepTwoSec 1
end sleepTwoSec 2
end sleepTwoSec 3
end sleepTwoSec 4
end blockingFunction
2s // not much more than 2 seconds
main end
I’ve tried to use suspendCoroutine
to make sleepTwoSec
suspend, but the output was the same:
suspend fun sleepTwoSec(n: Int) = suspendCoroutine {
println(" start sleepTwoSec $n")
sleep(2000)
println(" end sleepTwoSec $n")
it.resume(Unit)
}
>Solution :
In a coroutine, when you call blocking code, such as working with files/network or doing a time-consuming calculation, you must wrap that blocking work using a Dispatcher that can handle it. There are built-in dispatchers that can handle IO and long calculations, Dispatchers.IO
and Dispatchers.Default
that you can use, for example:
// In a coroutine or suspend function:
val result = withContext(Dispatchers.Default) {
doALongCalculation()
}
You can also create your own by calling asCoroutineDispatcher()
on an Executor or (rarely) by subclassing CoroutineDispatcher
.
You should never use Thread.sleep()
inside a coroutine. It blocks a thread, but it’s not doing IO or a calculation, so it is useless blocking and needlessly occupies a thread. If you use it with Dispatchers.IO
or Dispatchers.Default
, it is hanging onto one of their finite threads for nothing. If this happens in multiple places in your app, you could lock up coroutines throughout your app by occupying all the threads of the pool.
delay()
is the proper way to wait for an amount of time in a coroutine or suspend function. It doesn’t block a thread.
Your use of suspendCoroutine
is incorrect. You must not call blocking code inside the lambda without doing it on some other thread and then calling it.resume()
from that other thread when the work is done.