Does Kotlin delay use a dispatcher internally to unblock the caller thread? - kotlin

This is some test code I am using to learn kotlin coroutines. The code works as expected and takes about 1 Second to print the sum, but now If I replace delay(1000) by a blocking call like a network request, then the code takes about 10 seconds to print the sum ( each call takes around 1 second), but if I wrap the network call in a withContext and use the IO dispatcher it takes 1 second to print the sum, because it is run on a different thread. Does the delay function use some kind of a dispatcher to unblock the thread?
suspend fun asyncDoubleFn(num: Int): Int {
delay(1000)
return num * 2
}
fun main() = runBlocking {
launch {
val tt = measureTimeMillis {
val results = mutableListOf<Deferred<Int>>()
for (num in 0..10) {
val result = async { asyncDoubleFn(num + 1) }
results.add(result)
}
val sum = results.map { it.await() }.reduce { acc, i -> acc + i }
println("[SUM]: $sum")
}
println("[TT]: $tt")
}
launch {
println("Another coroutine")
}
println("Main Code")
}

Does the delay function use some kind of a dispatcher to unblock the thread?
Not just delay. All suspendable functions interact with the dispatcher.
The question you should ask is: "Which dispatcher is in charge here?"
And the answer is: runBlocking installs its own dispatcher that dispatches to the thread it is called on. Both launch and async in your code inherit it.
With this in mind:
If I replace delay(1000) by a blocking call like a network request, then the code takes about 10 seconds to print the sum (each call takes around 1 second)
Each blocking call will hold on to the single thread of the dispatcher. It won't be able to do any other work while being blocked.
but if I wrap the network call in a withContext and use the IO dispatcher it takes 1 second to print the sum, because it is run on a different thread
Yes, this changes the dispatcher, problem solved.
So, what does delay do? It suspends the current coroutine (the one that async launched) and returns the control to the dispatcher, which can now go on to resume your loop and start the next coroutine.

Related

Difference between GlobalScope and runBlocking when waiting for multiple async

I have a Kotlin Backend/server API using Ktor, and inside a certain endpoint's service logic I need to concurrently get details for a list of ids and then return it all to the client with the 200 response.
The way I wanted to do it is by using async{} and awaitAll()
However, I can't understand whether I should use runBlocking or GlobalScope.
What is really the difference here?
fun getDetails(): List<Detail> {
val fetched: MutableList<Details> = mutableListOf()
GlobalScope.launch { --> Option 1
runBlocking { ---> Option 2
Dispatchers.IO --> Option 3 (or any other dispatcher ..)
myIds.map { id ->
async {
val providerDetails = getDetails(id)
fetched += providerDetails
}
}.awaitAll()
}
return fetched
}
launch starts a coroutine that runs in parallel with your current code, so fetched would still be empty by the time your getDetails() function returns. The coroutine will continue running and mutating the List that you have passed out of the function while the code that retrieved the list already has the reference back and will be using it, so there's a pretty good chance of triggering a ConcurrentModificationException. Basically, this is not a viable solution at all.
runBlocking runs a coroutine while blocking the thread that called it. The coroutine will be completely finished before the return fetched line, so this will work if you are OK with blocking the calling thread.
Specifying a Dispatcher isn't an alternative to launch or runBlocking. It is an argument that you can add to either to determine the thread pool used for the coroutine and its children. Since you are doing IO and parallel work, you should probably be using runBlocking(Dispatchers.IO).
Your code can be simplified to avoid the extra, unnecessary mutable list:
fun getDetails(): List<Detail> = runBlocking(Dispatchers.IO) {
myIds.map { id ->
async {
getDetails(id)
}
}.awaitAll()
}
Note that this function will rethrow any exceptions thrown by getDetails().
If your project uses coroutines more generally, you probably have higher level coroutines running, in which case this should probably be a suspend function (non-blocking) instead:
suspend fun getDetails(): List<Detail> = withContext(Dispatchers.IO) {
myIds.map { id ->
async {
getDetails(id)
}
}.awaitAll()
}

launch long-running task then immediately send HTTP response

Using ktor HTTP server, I would like to launch a long-running task and immediately return a message to the calling client. The task is self-sufficient, it's capable of updating its status in a db, and a separate HTTP call returns its status (i.e. for a progress bar).
What I cannot seem to do is just launch the task in the background and respond. All my attempts at responding wait for the long-running task to complete. I have experimented with many configurations of runBlocking and coroutineScope but none are working for me.
// ktor route
get("/launchlongtask") {
val text: String = (myFunction(call.request.queryParameters["loops"]!!.toInt()))
println("myFunction returned")
call.respondText(text)
}
// in reality, this function is complex... the caller (route) is not able to
// determine the response string, it must be done here
suspend fun myFunction(loops : Int) : String {
runBlocking {
launch {
// long-running task, I want to launch it and move on
(1..loops).forEach {
println("this is loop $it")
delay(2000L)
// updates status in db here
}
}
println("returning")
// this string must be calculated in this function (or a sub-function)
return#runBlocking "we just launched $loops loops"
}
return "never get here" // actually we do get here in a coroutineScope
}
output:
returning
this is loop 1
this is loop 2
this is loop 3
this is loop 4
myFunction returned
expected:
returning
myFunction returned
(response sent)
this is loop 1
this is loop 2
this is loop 3
this is loop 4
Just to explain the issue with the code in your question, the problem is using runBlocking. This is meant as the bridge between the synchronous world and the async world of coroutines and
"the name of runBlocking means that the thread that runs it ... gets blocked for the duration of the call, until all the coroutines inside runBlocking { ... } complete their execution."
(from the Coroutine docs).
So in your first example, myFunction won't complete until your coroutine containing loop completes.
The correct approach is what you do in your answer, using CoroutineScope to launch your long-running task. One thing to point out is that you are just passing in a Job() as the CoroutineContext parameter to the CoroutineScope constructor. The CoroutineContext contains multiple things; Job, CoroutineDispatcher, CoroutineExceptionHandler... In this case, because you don't specifiy a CoroutineDispatcher it will use CoroutineDispatcher.Default. This is intended for CPU-intensive tasks and will be limited to "the number of CPU cores (with a minimum of 2)". This may or may not be want you want. An alternative is CoroutineDispatcher.IO - which has a default of 64 threads.
inspired by this answer by Lucas Milotich, I utilized CoroutineScope(Job()) and it seems to work:
suspend fun myFunction(loops : Int) : String {
CoroutineScope(Job()).launch {
// long-running task, I want to launch it and move on
(1..loops).forEach {
println("this is loop $it")
delay(2000L)
// updates status in db here
}
}
println("returning")
return "we just launched $loops loops"
}
not sure if this is resource-efficient, or the preferred way to go, but I don't see a whole lot of other documentation on the topic.

Kotlin coroutine delay waits for couroutine completion/fire and forget

I have the following code.
fun main(args: Array<String>) {
runBlocking {
while (true) {
launch {
println("Taking measurement")
val begin = System.currentTimeMillis()
while (System.currentTimeMillis() - begin < 20000) {
val test = 5 + 5
}
println("Took measurement")
}
println("After launch")
delay(1000)
println("After delay")
}
}
}
I would expect the output of that to be twice as many "After launch"s and "After delay"s in the first 20 seconds. SO the output should look something like this:
After launch
Taking measurement
After delay
After launch
Taking measurement
After delay
After launch
Taking measurement
After delay
After launch
Taking measurement
After delay
After launch
Taking measurement
... and then after 20s the first took measurements
But rather it looks like this and I can not wrap my head around to why it is not working.
After launch
Taking measurement
Took measurement
After delay
After launch
Taking measurement
What I basically want to achieve is a fire and forget. I want to start the code in launch and then 1 second after that I want to start it again.... The result of that code will be saved by that code, so I do not need any data back.
Any tips on why this is not working?
This is because the default dispatcher of runBlocking is a single-threaded dispatcher that uses the same thread that runBlocking was called on. Since it is single-threaded, it cannot run the blocking portions of your coroutine concurrently. If you use runBlocking(Dispatchers.Default) or launch(Dispatchers.Default) to avoid running in the single-threaded dispatcher, it will behave as you had expected.
However, the only reason you ran into this surprise in the first place is that you are trying to do blocking work directly in a coroutine, which you're not supposed to do. You should not do blocking work in a coroutine without wrapping it in withContext or a suspend function that uses withContext. If you replace your blocking loop in the coroutine with a delay(1000) suspend function call to simulate doing work in a non-blocking way, it will behave as you had expected.
When you do blocking work in a coroutine, you should either wrap it in a withContext call like this:
withContext(Dispatchers.Default) {
while (System.currentTimeMillis() - begin < 20000) {
val test = 5 + 5
}
}
or break it out into a suspend function that uses withContext to perform the blocking work on an appropriate dispatcher:
suspend fun doLongCalculation() = withContext(Dispatchers.Default) {
val begin = System.currentTimeMillis()
while (System.currentTimeMillis() - begin < 20000) {
val test = 5 + 5
}
}
This is why replacing your blocking work with a delay suspend function call is an adequate simulation of doing long-running work. delay and the doLongCalculation() above are both proper suspend functions that do not block their caller or expect their caller to be using a specific dispatcher to function correctly.

Why Kotlin coroutines run in the same thread sequentially?

I thought that calling a "suspend" function from coroutine context using launch makes the call asynchronous. But in the example below I see that 2 invocations of placeOrder method are not running in the same thread one after another.
What is my mistake?
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.File
fun main() = runBlocking {
t("1")
launch {
t("2")
placeOrder("C:\\Users")
t("3")
}
launch {
t("12")
placeOrder("C:\\Program Files")
t("13")
}
t("4")
}
fun t(s: String) {
val currentThread = Thread.currentThread()
println(s + ": " + currentThread.name + " " + currentThread.id)
}
suspend fun placeOrder(d:String): String {
t("placeOrder $d")
val user = createUser(d) // asynchronous call to user service
val order = createOrder(user) // asynchronous call to order service
t("placeOrder $d finished")
return order
}
suspend fun createUser(d:String): String {
t("createUser $d")
val toString = File(d).walk().map {
it.length()
}.sum().toString()
t("createUser $d finished")
return toString
}
suspend fun createOrder(user: String): String {
t("createOrder $user")
val toString = File("C:\\User").walk().map {
it.length()
}.sum().toString()
t("createOrder $user finished")
return toString
}
Output:
1: main 1
4: main 1
2: main 1
placeOrder C:\Users: main 1
createUser C:\Users: main 1
createUser C:\Users finished: main 1
createOrder 1094020270277: main 1
createOrder 1094020270277 finished: main 1
placeOrder C:\Users finished: main 1
3: main 1
12: main 1
placeOrder C:\Program Files: main 1
createUser C:\Program Files: main 1
createUser C:\Program Files finished: main 1
createOrder 5651227104: main 1
createOrder 5651227104 finished: main 1
placeOrder C:\Program Files finished: main 1
13: main 1
Instead of writing suspendable IO, you wrote blocking IO:
File(d).walk().map {
it.length()
}
Your functions never actually suspend and instead they block the single thread associated with their runBlocking dispatcher.
You gave your coroutines no opportunity to execute concurrently.
If you applied withContext(IO) { ... } around the above code, you'd get concurrency, but of the plain-old Java type, several threads being blocked in IO operations together.
The reason for this behavior is twofold:
All your coroutines are executed in the runBlocking scope, which is a single-threaded event loop. So this means only a single thread is ever used unless a different context is specified. (launch(Dispatchers.IO) as an example)
Even then it would be possible for the coroutines to interleave, except your coroutines do call suspending functions which actually have to suspend. This means it is effectively a normal sequential function call. If your functions included a yield() or delay(..) call you would see the coroutines interleave in execution.
launch function signature:
fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job (source)
As per the official Kotlin documentation link:
When launch { ... } is used without parameters, it inherits the context (and
thus dispatcher) from the CoroutineScope it is being launched from.
In your case, it inherits the context of the main runBlocking coroutine which runs in the main thread.
As coroutine context includes a coroutine dispatcher that determines what thread or threads the corresponding coroutine uses for its execution, so you can provide a different CoroutineContext with your launch coroutine builder. For Example:
fun main() = runBlocking {
t("1")
launch(Dispatchers.Default) {
t("2")
placeOrder("C:\\Users")
t("3")
}
launch(Dispatchers.Default) {
t("12")
placeOrder("C:\\Program Files")
t("13")
}
t("4")
}
Regarding suspend functions, a suspending function is just a regular Kotlin function with an additional suspend modifier which indicates that the function can suspend the execution of a coroutine. It doesn't make the call asynchronous by default.
You can execute your function code with custom dispatcher(e.g. IO dispatcher) using Kotlin's withContext() function as below:
suspend fun get(url: String) = withContext(Dispatchers.IO){/* Code for N/W logic */}
This will execute the body of the function in separate Thread than the calling coroutine context.
Here is 3-part series blog explaining usage of coroutines in Android apps :
https://medium.com/androiddevelopers/coroutines-on-android-part-i-getting-the-background-3e0e54d20bb
replace launch with async
read this
Essentially the code above is running synchronously even without runBlocking!
Since all the coroutines are launched into the main running on a single thread, ultimately you can use IO dispatcher which uses multiple threads.
Also, note that multiple coroutines can run on a single thread but they are never executed in parallel, they may appear as running in parallel because of thread switching from one coroutine to another when a new coroutine is launched or suspended.

Does Kotlin suspend funtion runs on a separate thread?

suspend funtions run on a seperate thread ?
If not, then what is the performance benefit ?
suspend fun requestToken():Token {..} // takes 2 sec to complete
suspend fun createPost (token:Token){..} // takes 3 sec to complete
suspend fun postItem() {
val token = requestToken()
val post =createPost(token)
processPost(post)
}
So, when we reach at processPost(post) and if suspend function do not run on a seperate thread then we have to wait for requestToken() and createPost(token) method
to complete (i.e 2+3= 5 seconds). As per the author, suspend is asyncronous,but if we are not spawning any new thread then how are we achieving asychronous behaviour ?
suspend is asynchronous
suspend funs execute synchronously with their caller. What you actually meant to say is "non-blocking" and that's a completely different story.
but if we are not spawning any new thread then how are we achieving asynchronous behaviour?
You are making the tacit assumption that all I/O must be blocking at some level. This is wrong. Non-blocking I/O works by pushing data to a send buffer and receiving notifications when there's data in the receive buffer. A suspend fun hooks into this mechanism by suspending itself after pushing the data to a send buffer and installing a callback that will resume it when response data is ready in the receive buffer.
Suspension-points can only be used within a coroutine context, for instance:
fun main() {
delay(1000)
}
Would not work because delay is a suspending function and the compiler wouldn't know how to handle that without a coroutine. When you do have a coroutine it can use something called a dispatcher to control thread ownership. Suspending means that the thread is no longer used to execute that part of your program but its doing something else or going idle. The way it works is that you can have several coroutines working at the same time without having a thread for each, that thread can then execute parts of each coroutine up to a suspension point. Generally the idea is that you can view suspending functions as "generators" which have stages for producing a result.
suspend fun hello() {
println("Hello")
delay(1000) // suspend here, let someone else use the thread while we wait
println("World")
delay(1000) // suspend again, we can still use the thread while waiting
println("done")
}
Everytime this is suspended it uses the thread to work on another function until that suspends and the delay expires, at that point this function will eventually resume execution up to the next suspension point or finish execution entirely.
This is different to blocking code as it does not waste the thread by putting it into wait state but rather borrows it to another function. That way no other threads need to be created to have concurrency as you can still work on multiple functions without true parallelism, instead it uses concurrent execution of parts of the functions.
Coroutines don't necessarily protect you from blocking, if you call Thread.sleep(1000) its still gonna be a blocking call. It is the responsibility of the programmer to use suspending equivalents to blocking functions in order to maximize effectiveness of this concept.
For more details, please read the documentation as its very detailed and helpful.
Background single thread or multiple background threads of a thread pool can be explicitly declared and then used, for example by passing it as a parameter, let's call this parameter "scheduler". The really cool thing about it is that initially started from the main thread it's automatically switched to the scheduler thread to execute a particular task on it and virtual machine kind of suspended or interrupted at this place and the thing which is even cooler the main thread gets unblocked and can execute something else while there is the task in background.
As soon as the task is finished, virtual machine sort of gets back to the point where it was suspended or interrupted before and then it resumes its execution from that point but now by also having the result returned from the background thread of the scheduler, below is the code snippet:
private val backgroundThread = ThreadPoolExecutor(1, 1, 15L, TimeUnit.SECONDS, LinkedBlockingQueue())
GlobalScope.launch(Dispatchers.Main, CoroutineStart.DEFAULT) {
postItem(backgroundThread))
}
suspend fun CoroutineScope.postItem(scheduler: ThreadPoolExecutor): Boolean {
val token = requestToken(scheduler)
val post = createPost(token, scheduler)
return processPost(post, scheduler)
}
private suspend fun CoroutineScope.requestToken(scheduler: ThreadPoolExecutor): String {
val def: Deferred<String?> = async(scheduler.asCoroutineDispatcher(), CoroutineStart.DEFAULT) {
val token = networkApi.requestToken()
}
return def.await() ?: ""
}
private suspend fun CoroutineScope.createPost(token: String, scheduler: ThreadPoolExecutor): String {
val def: Deferred<String?> = async(scheduler.asCoroutineDispatcher(), CoroutineStart.DEFAULT) {
val post = networkApi.createPost(token)
}
return def.await() ?: ""
}
private suspend fun CoroutineScope.processPost(post: String, scheduler: ThreadPoolExecutor): Boolean {
val def: Deferred<Boolean?> = async(scheduler.asCoroutineDispatcher(), CoroutineStart.DEFAULT) {
val result = networkApi.processPost(post)
}
return def.await() ?: false
}