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.
Related
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.
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
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
read a lot about this but still there are some unclear things.
The sentence: "coroutines enable writing asynchronous code in synchronous way" is a little bit confusing.
Coroutines are ALWAYS asynchronus. they are basically running on the threads all the time? It is on us to decide which threads will coroutines we declare use (Mainscope- they run on MainThread, CoroutinesScope - it will run on some different threads that are not associated with UI thread??, ViewmodelScope - they will run on the threads that are associated with viewmodel??)
Did I get this right?
The traditional way most java world write asynchronous is by using "callbacks", which they think and was the only way to write asynchronous tasks by adding listeners for success and failures.
But coroutines way is slightly different. Actually you call a suspend function like delay(ms: Long) and it doesn't seem asynchronous because there isn't any callbacks involved here, but behind the hood the Continuation object would work in place of callbacks to resume it later when needed.
Coroutines are ALWAYS asynchronus
Yes, they are!
They are basically running on the threads all the time?
Yes, they are!
It is on us to decide which threads will coroutines we declare use
Yep, absolutely. They implement Structured Concurrency.
Mainscope - they run on MainThread
Apparantly, yes.
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
It creates a CoroutineScope with Context's dispatcher element as Dispatchers.Main to make the children dispatch into the main thread by default (when not specified), and attaches a SupervisorJob to make other children do not being affected by cancellation of one.
CoroutinesScope - it will run on some different threads that are not associated with UI thread??
No, it is upto you to provide the CoroutineDispatcher element to the CoroutineContext that will decide which thread it need to dispatch child coroutines to, for example there are 4 default CoroutineDispatcher:
Dispatchers.Main - Dispatches to main thread (single-threaded)
Dispatchers.Default - Dispatches to CommonPool (max thread-count same as cores in your processor)
Dispatchers.IO - Dispatches to CommonPool (max thread-count: 64)
Dispatchers.Unconfined - Dispatches to any thread registered (usually for very small tasks, and is not recommended to be used anyway)
Moreover you can make your own CoroutineDispatcher using java.util.Executors:
Executors.newFixedThreadPool(4).asCoroutineDispatcher() // Your own dispatcher max of 4-threads
There are many other elements in CoroutineContext other than Dispatcher. Like: Job, CoroutineName, CoroutineInterceptor, CoroutineExceptionHandler, etc. I'd recommend reading out this article for better visualization of what's happening with some of the most important Elements of CoroutineContext.
A CoroutineScope is just a wrapper for the CoroutineContext which essentially helps in launching a coroutine using launch or async, you can create one by passing a CoroutineContext as a parameter like: CoroutineScope(Dispatchers.Default + Job() + CoroutineName("My Coroutine")).
Note: All the elements of the CoroutineContext is itself a CoroutineContext, the + sign is overload of plus function defined in CoroutineContext essentially overriding elements from left CoroutineContext with elements from right CoroutineContext (if element does not exist in left it'll just add it).
ViewmodelScope - they will run on the threads that are associated with viewmodel??
ViewModelScope implements Dispatchers.Main by default to launch tasks and they can change UI elements as only main thread can change the UI elements.
SideNote: You can change element of CoroutineContext before launching (overriding one of scope).
Example:
viewModelScope.launch(Dispatchers.Default) {
// This coroutine is launched under Dispatchers.Default (backed by a CommonPool of threads) instead of Dispatchers.Main
// but will follow cancellation as soon as lifecycle of Activity associated is destroyed as Job element is not overriden
}
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.