Kotlin Coroutine Execution Order Same Thread - kotlin

I have come across some code that creates a CoroutineScope with a single thread context
val serialThreadContext = newSingleThreadContext("mysinglethreadcontext")
fun myScope(): CoroutineScope = CoroutineScope(serialThreadContext)
Then elsewhere in the code base, coroutines are launched in this scope...
myScope().launch {
someOtherMethod()
...
}
From what I understand, all of these created coroutines will be scheduled on the same thread, but I can't find any specific documentation on order of execution or suspension.
This raises the following questions in my mind:
if someOtherMethod doesn't contain any suspend functions, can this coroutine still be suspended by the thread?
is there any guaranteed order of execution when the thread pulls the coroutine from the scheduler?

Coroutines can only be suspended at suspend functions. Once someOtherMethod is invoked, if it is not a suspend function, there is no way to avoid waiting for that function to return before the thread can be freed up by suspending. Note that a function being marked suspend still doesn't mean it will necessarily suspend the coroutine calling it. If it doesn't internally do suspending work on some other dispatcher, it will still be occupying your calling thread.
There is no guarantee of execution order. You should not rely on a single-threaded dispatcher to try to queue work in order. See my answer here for an example of how you could create a work queue with a Channel.

Related

diff between executing coroutine with and without suspend-ed IO methods

Let's assume I have the code:
// Android ViewModel
class MyVM(val dao: MyDao) : ViewModel() {
fun onButtonInsert() {
// point 1
viewModelScope.launch {
// point 2
dao.insert_SuspendFun(MyData(id=1, "hello, it works"))
// point 3
dao.insert_NOT_SuspendFun(MyData(id=2, "hello, it fails"))
// point 4
}
}
}
// Room DAO
#Dao
interface MyDao {
#Insert
suspend fun insert_SuspendFun(md: MyData)
#Insert
fun insert_NOT_SuspendFun(md: MyData)
}
Now when fun onButtonInsert runs then:
1st line works:
dao.insert_SuspendFun(MyData(id=1, "hello, it works"))
but 2nd line:
dao.insert_NOT_SuspendFun(MyData(id=2, "hello, it fails"))
fails with the exception:
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
"The only difference" is that fun insert_NOT_SuspendFun has no suspend keyword.
Both methods run in the same coroutine.
Can someone explain what happens under the hood?
How are threads working in this coroutine?
Why does 1st call use non-UI thread but 2nd uses UI thread?
Thanks! ;]
Room generates the code for the implementation of your DAO, so the behaviors are based on their design decisions.
The non-suspend function runs on whatever thread you call it from, which is the only possible way a non-suspend function can run. Since Google doesn’t want people making janky apps that do IO on the main thread, they decided to make the generated implementation under the hood check the thread and throw an exception if it’s the main thread.
The suspend function implementation that Room generates suspends while the IO is done on a background thread. This follows the Kotlin coroutines convention that a suspend function should never block the thread it is called from.
Any suspend function can internally suspend the calling code until they finish and resume. Other than by calling other suspend functions, this is most commonly done using withContext, coroutineScope, suspendCoroutine, or suspendCancellableCoroutine. Those last two are low level and allow the coroutine to be suspended and then resumed at will from any other thread.
I have not checked the generated source, but I think it’s likely Room generates the suspend function implementation by using suspendCancellableCoroutine and runs the IO work using an internal thread pool before resuming the coroutine.
An alternative way they could have done it is by using withContext(Dispatchers.IO) and then done the blocking IO work within it. This is possibly the most common and easiest way to do blocking work in a coroutine. It uses the IO Dispatcher (a thread pool provided by Kotlin coroutines).
As for why your coroutine runs on the main thread, it’s because viewModelScope’s attached dispatcher uses the main thread. You can override it by passing a specific dispatcher to your launch call, but this is not often done if you’re following conventions. Android is full of functions that must be called from the main thread, so it usually looks cleanest to keep coroutines on the main dispatcher and only do the background work using suspend functions and withContext blocks. Exception would be a coroutine that runs blocking code and doesn’t need to do anything on the main thread.
You're trying to run a long-running synchronize task in side main thread as it does not mention suspend keyword to make it async.
You can use both methods in the IO thread instead main;
viewmodelscope.launch(dispatchers.io)

Kotlin launch vs launch(Dispatchers.Default)

I was testing out how blocking code and coroutines interact for my own understanding. Obviously, we should use non-blocking but I wanted to understand what would happen if there was some blocking code in there.
If I run the following:
fun main() {
println("Start: ${Thread.currentThread()}")
runBlocking {
println("Run Blocking : ${Thread.currentThread()}")
launch(Dispatchers.D) { // Immediately scheduled for execution
println("Inside launch: ${Thread.currentThread()}")
}
Thread.sleep(10000)
println("Going to do some other stuff now")
}
println("Out of runBlocking")
}
everything runs in the same coroutine and Inside launch: doesn't appear until the Thread.sleep has completed. I assume this is because it is blocking, so no suspending, or use of another coroutine can take place.
If I pass Dispatchers.Default to launch (I thought launch used Dispatchers.Default by default, if you didn't specify one?), the launch block runs in a separate coroutine and starts immediately.
So, why does launch(Dispatchers.Default) allow the second coroutine to start and why doesn't just launch ?
I thought launch used Dispatchers.Default by default, if you didn't specify one
This is where you're mistaken. Here is the documentation:
The coroutine context is inherited from a CoroutineScope. Additional context elements can be specified with context argument. If the context does not have any dispatcher nor any other ContinuationInterceptor, then Dispatchers.Default is used.
This means that if you don't pass a dispatcher to launch, it will default to the dispatcher present in the context of the CoroutineScope in which you started that launch. If the coroutine context doesn't have a dispatcher, then launch will use Dispatchers.Default.
So in your case, a parameterless launch inherits the context and thus the dispatcher provided by runBlocking, which is a single-threaded dispatcher running on the thread that it blocks (the main thread here).
why does launch(Dispatchers.Default) allow the second coroutine to start and why doesn't just launch ?
This is because, as we've just seen, these 2 cases don't involve the same threads. With launch(Dispatchers.Default), the launched coroutine runs on a separate thread pool and can run in parallel of the main one. However, with the parameterless launch, the second coroutine runs on the same thread as the rest of the code, because it runs in the single-threaded dispatcher of runBlocking, which is backed by the main thread.
The last piece of the puzzle is that Thread.sleep is blocking (as you said), unlike delay which is the suspend equivalent from the coroutines library. When there is only one thread involved (in the case of the parameterless launch), running the last Thread.sleep blocks the main thread and never gives a chance to the other coroutine to run. You would need something to suspend execution, for instance delay().

Confused about Kotlin's suspending functions in relation to coroutines

If I understand correctly, a suspend function is something you can execute inside a coroutine and what suspend functions do is that they suspend the coroutine so that another coroutine may start doing work. Now, does this mean that while the coroutine is suspended that the suspend function is still doing work? This is what confuses me because I've seen many texts say that it pauses not only the coroutine but also the suspend function. But if it pauses the suspend function as well then whats the point if the work you wanted to be done is paused?
Suppose I had a suspend function that takes 30 seconds to finish doing some mathematical calculation. And then I had another coroutine that said printed some simple text like "Hello, World". If the first coroutine suspends and goes to the second coroutine to print the greeting, would mathematical calculation still be happening at the same time?
Now, does this mean that while the coroutine is suspended that the suspend function is still doing work?
No, it means that other functions can do work while it is suspended.
This is what confuses me because I've seen many texts say that it pauses not only the coroutine but also the suspend function.
This is correct.
But if it pauses the suspend function as well then whats the point if the work you wanted to be done is paused?
The main use case is delegating work to external resources, especially the networking layer. While your function is awaiting the data to come in from the network, it has nothing else to do. In the meantime other coroutines may go on executing on the same thread.
Suppose I had a suspend function that takes 30 seconds to finish doing some mathematical calculation.
That function, even though declared suspendable, probably wouldn't actually suspend itself. It would keep occupying the calling thread.
And then I had another coroutine that said printed some simple text like "Hello, World".
If you're on a single-threaded dispatcher, that coroutine would have to wait its turn until the first coroutine explicitly suspends itself, which would probably not happen inside the CPU-intensive function you mention.
If the first coroutine suspends and goes to the second coroutine to print the greeting, would mathematical calculation still be happening at the same time?
No, a suspended coroutine doesn't do any work.
For example, the first coroutine may call yield() within its computation loop and thus allow Kotlin to suspend it and run some other coroutines on the same thread. It wouldn't make any progress while suspended.
You can see it like this: let's say there is a single thread running all coroutines. The manner in which they will run is called cooperative concurrency. A coroutine must explicitly suspend itself to allow others to run. This is very similar to several threads running concurrently on a single CPU core, except that the OS can do one more "magical" thing: it can suspend the thread at any time, without that thread's permission. Which is why this kind is called pre-emptive concurrency.

Suspending IO-bound and CPU-bound functions in Kotlin's coroutines

Executing an IO-bound function (say, requesting data from a backend) in a coroutine gives me the advantage of suspending it's execution until the result of the request becomes available, right? However, a CPU-bound function (parsing a huge text file, for example) doesn't 'wait' for anything, it just does a lot of work. So, does executing it inside a coroutine NOT give me the advantage of suspending its execution? The only (valuable) advantage coroutines give me when it comes to CPU-bound functions is being able to choose the thread (or pool of threads) that will be blocked while executing the function, am I correct?
suspend functions do not automatically make the function as not blocking the thread, like in the following case as it would still block the calling thread ( dispatcher associated with the scope used to launch the coroutine)
suspend fun findBigPrime(): BigInteger =
BigInteger.probablePrime(4096, Random())
suspend functions can be made into a non-blocking thread function by using withContext like the following,
suspend fun findBigPrime(): BigInteger =
withContext(Dispatchers.Default) {
BigInteger.probablePrime(4096, Random())
}
This causes coroutine launched from main/caller thread to be blocking but not the thread itself to be blocking.
https://medium.com/#elizarov/blocking-threads-suspending-coroutines-d33e11bf4761
So, does executing it inside a coroutine NOT give me the advantage of suspending its execution?
I am not sure I understand what you mean by that, but calling a suspending function will suspend the calling coroutine anyway, whatever dispatcher you choose, and that doesn't depend on what's inside the function as far as I know.
However, please note that calling a blocking function from inside a coroutine will make this coroutine block the thread executing it, there is no magic here. That's why blocking operations should be avoided inside coroutines.
The only (valuable) advantage coroutines give me when it comes to CPU-bound functions is being able to choose the thread (or pool of threads) that will be blocked while executing the function, am I correct?
Using Dispachers.IO or Dispatchers.Default only has the effect of choosing a different pool of threads, yes.
In case of IO, the pool will spawn as many threads as necessary, because they can all be "blocked on I/O".
In case of Default, only a number of threads proportional to the number of cores will ever be created, because there would be no point for CPU-bound tasks to create more threads than cores (if all the cores are busy, then context switching can only degrade the overall performance).

why coroutines 1st run on caller thread but after first suspension point it runs on DefaultExecutor in “Dispatchers.Unconfined”?

Below code prints
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
launch(Dispatchers.Unconfined) {
// not confined -- will work with main thread
println("thread ${Thread.currentThread().name}")
delay(500)
println("thread ${Thread.currentThread().name}")
}
}
1st run on caller thread but after first suspension point it runs on DefaultExecutor in “Dispatchers.Unconfined”
thread main
thread kotlinx.coroutines.DefaultExecutor
Read the description of Dispatchers.Unconfined, it explains exactly this behavior:
A coroutine dispatcher that is not confined to any specific thread. It executes initial continuation of the coroutine immediately in the current call-frame and lets the coroutine resume in whatever thread that is used by the corresponding suspending function, without mandating any specific threading policy. Note: use with extreme caution, not for general code.
The Unconfined dispatcher has no thread associated with it. In almost all circumstances the coroutine resumes on the thread where the users calls continuation.resume().
In the specific case you're showing, you call the standard function delay so Kotlin must internally handle both its suspension and resumption. To make this happen, it maintains a scheduled thread pool to which it submits the resumption task, scheduled with the delay you specify.
The Unconfined dispatcher is useful only in specialized scenarios, where you write your own infrastructure and want to be in control of the thread where the coroutine resumes. Basically, it short-circuits the entire dispatcher mechanism.