What happens using a Dispatcher v/s Main Thread in Android - android

In the context of Android, What is the difference between a dispatcher vs main thread.
As per my understanding referring the documentation,
It is backed by a shared pool of threads on JVM. By default, the
maximal level of parallelism used by this dispatcher is equal to the
number of CPU cores, but is at least two. Level of parallelism X
guarantees that no more than X tasks can be executed in this
dispatcher in parallel.
Will it spawn a new thread or there will be as per the log name DefaultDispatcher-worker-1 a worker that will communicate with the pool of threads other than the main to handle a block of co-routine or worker itself is a Co-routine?
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val timeInMillis = measureTimeMillis {
GlobalScope.launch(Dispatchers.Default) {
Log.d(TAG, "Starting coroutine in thread ${Thread.currentThread().name}")
val answer = doNetworkCall()
withContext(Dispatchers.Main) {
Log.d(TAG, "Setting text in thread ${Thread.currentThread().name}")
}
}
}
Log.d(TAG, "(The operation took $timeInMillis ms)")
}

A Dispatcher is essentially a thread pool. When you use launch(Dispatchers.Default), the Thread used to run the code in the coroutine will be obtained from Dispatchers.Default. Each time there is a suspend function call in the coroutine, when the coroutine resumes after that call, it might be resuming on a different Thread instance coming from the same Dispatchers.Default thread pool.
"DefaultDispatcher-worker-1" is the name of a literal Thread instance that came from the Dispatcher.Default's pool.
withContext is itself a suspend function call, so any code after the withContext block will also be resumed on some thread from Dispatchers.Default. (There is none in your example).
The code inside withContext(Dispatchers.Main) will be run on a thread from Dispatchers.Main.
Dispatchers.Main is a special Dispatcher that has only one thread, and that thread is the same Main Thread used by the OS.
You should rarely ever need to use GlobalScope and it is discouraged because it doesn't allow easy management of coroutine lifecycles. If you use lifecycleScope instead, your coroutines will be automatically cancelled when the associated Activity or Fragment is shut down. This is usually what you want, because an Activity or Fragment shouldn't be continuing to do work after it's gone.
Usually on Android, most coroutines should be launched from lifecycleScope or viewModelScope and should not need to have a Dispatcher specified since these scopes by default use Dispatchers.Main which is usually what you want. (Actually they use a different dispatcher called Dispatchers.Main.immediate which also uses the main thread but also can run the first part of a coroutine immediately without deferring to the next frame of the main thread loop. Not a distinction you need to worry about.) You can wrap the pieces of your coroutine that need other dispatchers in withContext. You don't need to do this if you are only calling suspend functions. By convention it is up to suspend functions to internally delegate to a specific Dispatcher if they need to.
An exception to the above paragraph might be if you're launching a coroutine in viewModelScope that does some blocking work and never touches anything that is main-thread-only. Then you can skip withContext and specify a dispatcher explicitly with launch.

Related

MainScope vs GlobalScope

What is the difference between GlobalScope and MainScope?
//Accessing data from Room
GlobalScope.launch {
v.tvStoreName.text = pfViewModel.getStoreName()
pageDetails.pageNumber = currentPage
pageDetails.pageSize = pageSize
pfViewModel.getTransactions(pageDetails, toolbarBuilder?.getDate()!!)
}
The GlobalScope sometimes makes an error which is very hard to reproduce.
Fatal Exception:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the
original thread that created a view hierarchy can touch its views.
MainScope().launch {
var storeName = ""
withContext(Dispatchers.Default) {
storeName = pfViewModel.getStoreName()
}
v.tvStoreName.text = storeName
}
What is the difference between GlobalScope and MainScope?
MainScope is a CoroutineScope that uses the Dispatchers.Main dispatcher by default, which is bound to the main UI thread.
The GlobalScope is a CoroutineScope that has no dispatcher in its coroutine context. This means that the coroutines launched in this scope will use the Dispatchers.Default dispatcher, which is backed by a thread pool (sized based on the number of CPU cores you have).
The GlobalScope also has no Job in its context, which means structured concurrency doesn't apply. Coroutines launched in it are never cancelled automatically, so they need to be manually controlled. This is why it is usually discouraged to use it unless you have very specific needs.
Only the original thread that created a view hierarchy can touch its views.
This error occurs when you're trying to modify views from outside the main thread, which is what happens if you do this from coroutines launched in the GlobalScope (because it's backed by a separate thread pool).
In your second snippet, you are using withContext(Dispatchers.Default), which only makes this part of the code run on that thread pool, but the rest runs on UI thread. This is why the update of the UI is ok there.
Note that Room already uses a dispatcher with a background thread pool for its queries, so you don't need to switch context manually like this, you can just call it from the UI thread.
Side note: using MainScope().launch { .. } like this is a bad idea, because it suffers from the same cancellation issue as GlobalScope. To use it properly, you would need to extract this scope into a variable/property so you can cancel it when appropriate. That said, it's easier to use an existing scope. Android already provides a ready-to-use coroutine scope in components such as Activities which have a lifecycle (see lifecycle-runtime-ktx library). It's called lifecycleScope. You should launch your coroutines in this scope so they automatically get cancelled when the activity is destroyed.

Which Coroutine Dispatcher to use for running code in a loop

In my Android application, I have a usecase in which I need to run a bit of code inside a loop for as long as the app is running.
Example code:
fun exampleRunCodeInLoop() {
viewModelScope.launch(Dispatchers.Default) {
while (true) {
delay(5000) // Wait for 5 seconds
// #param someCondition is just an example for the if statement
if (someCondition == true) {
withContext(Dispatchers.Main) {
// Do something on Main thread
}
}
}
}
}
I am confused regarding which Dispatcher to use for the launch block to get the best performance. Should I use Default, IO or just run everything on Main?
At least here's the usage I did with coroutine dispatcher.
Dispatchers.Default - I usually use this when only doing CPU-intensive tasks to parallel with the current task on the main thread, it has a fixed size hardcoded to the number of available processors, because that's what makes sense for CPU-intensive tasks. Use with operation like loops, math operations, data/image manipulations.
Dispatchers.IO - is designed for offloading blocking IO tasks to a shared pool of threads. Use if you still need more threads than cores if you're doing blocking IO, e.g., Database query is an IO operation or network calls
Dispatchers.Main - A coroutine dispatcher that is confined to the Main thread operating with UI objects. This dispatcher can be used either directly or via MainScope factory. Usually such dispatcher is single-threaded. Usually used when you are done or finished with the background task from IO or Default to forecast whatever results from operations you've done to the UI or Main Thread.
If you are doing IO, use the IO one
If you are doing something CPU intensive, use the Default one
In your code example that does not do heavy computation or IO, there is nothing that blocks, so you can launch in Dispatchers.Main and never have to switch contexts for anything. delay is a suspend function, so it doesn't block. You never have to worry about which dispatcher you're on when you call a suspend function (unless someone doesn't know what they're doing and has composed a suspend function that does block).
If you were not doing everything on the Main thread, you'd be switching back and forth between dispatchers anyway so it doesn't matter which Dispatcher you launch in as long as you wrap the appropriate sections in withContext and use the appropriate dispatcher. A general convention is if your coroutine interacts with the UI, launch it in Main and wrap the non-main parts. This follows the convention of all the non-coroutine code in Android which is written to be called from the main thread in the majority of methods.
Dispatchers.Default as it is designed to be optimal for computations, by using a shared pool of threads that contains a maximum number of threads that is equal to the number of CPU cores
Dispatchers.IO in case your blocking the thread, waiting for a response without doing heavy computational logic

Does runBlocking(Dispatchers.IO) block the main thread even the the coroutine context is not Dispatchers.Main?

I'm learning Android with Kotlin and I have learned that the recommended way to start coroutines without blocking the main thread is to do something like below
MainScope().launch {
withContext(Dispatchers.IO) {
// Do IO work here
}
}
But I was also wondering if the call below not would block the main thread because it's still using Dispatchers.IO
runBlocking(Dispatchers.IO) {
// Do IO work here
}
If you call runBlocking(Dispatchers.IO) from the main-thread, then the main-thread will be blocked while the coroutine finishes on the IO-dispatcher.
This is what the documentation says about this:
When CoroutineDispatcher is explicitly specified in the context, then
the new coroutine runs in the context of the specified dispatcher
while the current thread is blocked. If the specified dispatcher is an
event loop of another runBlocking, then this invocation uses the outer
event loop.
You can find the documentation here: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
Dispatcher typically does not lead to an actual switching to another thread. In such scenarios,
* the underlying implementation attempts to keep the execution on the same thread on a best-effort basis.
https://github.com/Kotlin/kotlinx.coroutines/pull/3236/files

Do suspend functions execute in another thread and come back with the result?

I've looked at plenty of articles online but I'm still a bit confused as to what happens specifically, step by step, when suspend functions are suspended in coroutines.
I know that a suspend function, under the hood, is just a regular function with a Continuation parameter that allows it to resume, but my confusion is regarding where that suspend function or coroutine goes and where it comes back once resumed.
I've heard a few people saying "they don't necessarily come back to the same thread" and I don't get it, can someone please explain this to me step by step?
TLDR;
There is no guarantee, it may or may not,
it really depends that on the following points:
Is the dispatcher is multi-threaded?
Is there any dispatcher override in between?
LONG Answer
A coroutine has a CoroutineContext that specify how it behaves, where it run.
A CoroutineContext is mainly build up with four elements: Job, CoroutineName, CoroutineExceptionHandler and Dispatcher.
Its responsibility of the dispatcher to dispatch the coroutine. A dispatcher can be paused to stop coroutines to even run (this is useful in unit testing) mentioned here in the android conference talk, it may be a single-threaded dispatcher just like Dispatchers.Main, it has an event-loop like javascript has.
So, it really depends that on the following points:
Is the dispatcher is multi-threaded?
For example: This will run on single thread.
suspend fun main() {
val dispatcherScope = CoroutineScope(Executors.newSingleThreadExecutor().asCoroutineDispatcher())
val job = dispatcherScope.launch {
repeat(10) {
launch {
println("I'm working in thread ${Thread.currentThread().name}")
// every coroutine on same thread
}
}
}
job.join()
}
Run it here, other single threaded dispatchers: Dispatchers.Main
Is there any dispatcher override in between?
With the same context, if we override the dispatcher before launch, it will change the thread even if original context is based on single-threaded event-loop, each coroutine will run on different thread creating 10 different thread:
dispatcherScope.launch {
repeat(10) {
launch(Dispatchers.IO) {
println("I'm working in thread ${Thread.currentThread().name}")
// every coroutine on same thread
}
}
}
Run it here, other multi-threaded Dispatchers: Dispatchers.Default, Executor based dispatcher, Dispatchers.Unconfined (this launch coroutine in any free thread).
The short answer is: they execute somewhere and come back with the result to somewhere.
The long(er) explanation for the short answer:
"Somewhere" might be the same thread, it might be a different thread - it depends on the dispatcher and in many cases, the current state of the dispatcher. For instance, the contents of a SequenceScope will (by default) run on the same thread.
Another case where a suspend function might run on the same thread is if it's using the same dispatcher as the calling function.
Dispatchers can also share threads between them to save on thread creation (and just keep a count of the maximum number of parallel operations for their own tasks), so even switching between different dispatchers that each use thread pools may not result in using a different thread.
As far as people saying "they don't necessarily come back to the same thread," this is correct, for similar reasons. Keep in mind that your dispatcher may have many threads, and the one that you were using before you got suspended might be occupied with a different function right now, so the dispatcher will simply pick a different thread to run your code on.
When the coroutine suspends, the underlying Java method returns a special COROUTINE_SUSPENDED value. If the calling function is also suspendable, it also returns the object, and so the execution returns to the innermost plain, non-suspendable function. This function typically runs an event loop, where event handlers are calls to continuation.resume(). So now it is ready to take the next handler from the queue and resume another coroutine.
When you call continuation.resume(), the continuation itself knows about the dispatcher in charge of the coroutine and delegates to it. If the current thread isn't owned by that dispatcher, it dispatches an event to another event loop, the one served by the dispatcher's thread pool. This way the dispatcher controls the thread where the corutine resumes.

What is the order of execution with coroutines?

Consider the following code in kotlin.
val scope = CoroutineScope(Dispatchers.Main + Job())
scope.launch {
println("inside coroutine")
}
println("outside coroutine")
We create a coroutine in the Main(UI) thread and there is some code after the coroutine.
I know it doesn´t make much sense to do that in real code, but it´s just a theoretical question.
Considering that the coroutine runs in the Main thread, why println("outside coroutine") is ALWAYS executed first?
I would have expected that sometimes i would see first outside coroutine and other times, first inside coroutine, kind of like two threads.
Who (OS or Coroutines implementation) decides that the coe outside the coroutine is run first?
Considering that the coroutine runs in the Main thread, why println("outside coroutine") is ALWAYS executed first?
Let's imagine that your code instead was this:
someView.post {
println("inside post")
}
println("outside post")
Here, we create a Runnable (lambda expression) and pass that to post() on some View. post() says that the Runnable will be run() on the main application thread... eventually. That Runnable is put on the work queue that the Looper powering the main application thread uses, and it gets executed when that Runnable gets to the top of the queue (more or less — the details are messier IIRC but not important here).
But if you are executing this code on the main application thread, println("outside post") will always be printed first. The Runnable is placed onto the queue to be executed later, but you are still executing on the main application thread, and so even if the queue were empty, that Runnable will not run until you return control of the main application thread back to Android. So, after the call to post(), execution continues with println("outside post").
Under the covers, Dispatchers.Main is basically using post() (again, the details are more complicated but not too important for this discussion). So, when you launch() the coroutine, that lambda expression gets queued up to be executed eventually on the main application. But, you are already on the main application thread, so execution continues normally, and the println("outside post") gets printed before the coroutine gets a chance to do anything.
Suppose that your code instead was:
val scope = CoroutineScope(Dispatchers.Main + Job())
scope.launch {
println("inside coroutine")
}
scope.launch {
println("inside another coroutine")
}
Now you are in a situation where in theory either of those lines could be printed first. You are queuing up both lambda expressions, and it is up to the dispatcher to decide what to run on what thread at what point. In practice, it would not surprise me if "inside coroutine" is always printed first, as a simple implementation of Dispatchers.Main would use FIFO ordering in the absence of other constraints (e.g., a coroutine is blocked on I/O). However, you should not assume a particular order of invocation of those two coroutines.

Categories

Resources