Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin difference between CoroutineScope and withContext

To change the thread in a function I use either CoroutineScope or withContext. I don't know's the difference, but with CourineScope I can also use a handler.

Examples:

private fun removeViews(){
    CoroutineScope(Main).launch(handler){
        gridRoot.removeAllViews()
    }
}

private suspend fun removeViews(){
    withContext(Main){
        gridRoot.removeAllViews()
    }
}

I call this function from a coroutine that works on background thread (IO). Is any more appropriate than the other?

like image 220
GeoCap Avatar asked Sep 15 '20 13:09

GeoCap


3 Answers

These two are actually radically different and you just happen to have a use case where you don't experience the difference:

CoroutineScope(Main).launch(handler){

This launches a concurrent coroutine that goes on independently.

withContext(Main){

This is a function that completes only when the code inside it completes, and returns its result. This is the way you should be doing it.

The first approach, with CoroutineScope, has another deficiency in that it circumvents structured concurrency. You create an ad-hoc coroutine scope that has no parent and thus won't be automatically cleaned up if it takes a longer time to complete and your GUI is dropped (user navigates away from the current Activity).

You should actually never use the CoroutineScope(Main) idiom, I don't think there's a single instance where it would be appropriate. If you explicitly want to avoid structured concurrency, it is still better and cleaner to write

GlobalScope.launch(Main + handler) {

and has pretty much the same effect.

If you want a concurrent coroutine that fits into structured concurrency, use

fun CoroutineScope.removeViews() {
    launch {
        gridRoot.removeAllViews()
    }
}

Note I removed the handler argument, a child coroutine ignores it because it forwards any failures to its parent coroutine, which is exactly what you want. The parent coroutine should have an exception handler installed.

like image 189
Marko Topolnik Avatar answered Oct 22 '22 18:10

Marko Topolnik


Technically both are same but when it comes to use case both are different and has big impact on the different use cases so be careful while using them
Coroutine Scope:
CoroutineScope is a starting Point of Coroutine. CoroutineScope can have more than one coroutine within itself, which makes coroutine hierarchy. Lets think, Parent has more than one children. Think CoroutineScope is a parent and this parent can have more than one child which are also coroutines. These childrens are known as job

private val coroutineScope = CoroutineScope()
    coroutineScope(IO).launch{
    val childOne = launch(Main){}
   val childTwo = launch(Main){}
}

see that childOne and childTwo? why we need these? because we can't directly cancel the coroutine there is no such way the coroutine can be cancelled directly, either the coroutine gets completed or it gets failed. But what if we wanna cancel it? in such cases we need job. But thing to be notice here these job children are totally associated with parent. And Parent is (IO) and childrens are (Main), this parent is started in IO Disptacher but when it comes to those childrens they are gonna switch to (Main) and do their thing but the parent will still be at (IO) switching the Dispatcher of childrens not gonna effect parent.
But what happens if something wrong happens to either of the children, in that case we will watch this summit:
https://www.youtube.com/watch?v=w0kfnydnFWI
This summit about coroutine exception and cancellation. watch it, its amazing...

withContext:
What is withContext?
withContext should be inside any Coroutine or suspend fun because withContext itself is a suspending function.
withContext is use to switch the context in different situation
but how?


suspend fun fetchFromNetworkAndUpdateUI() {
    withContext(IO){
        println("Some Fake data from network")
    }
    
    withContext(Main){
        //updating Ui
        //setting that Data to some TextView etc
    }
    
}

see the code, we are fetching the data asynchronously from network cause we don't wanna block the MainThread and then we switch the context, why? cause we can't update UI related stuff in IoDispatcher that's we have change the context to main with withContext(main){} and update the UI.
and there are other use cases like liveData, we are fetching the value using retrofit using IoDispatcher then in next step we have to set it to the liveData by using withContext(main){} cause we can't observe liveData's value in background thread.
yeah, I hope this helps. comment if there is any question.

like image 20
USMAN osman Avatar answered Oct 22 '22 19:10

USMAN osman


From the Antonio Leiva article about coroutines:

The coroutine context is a set of rules and configurations that define how the coroutine will be executed

withContext is a function that allows you to easily change the context of a suspending function, in order to be sure that that function is executed in a particular thread (E.g. Thread from IO pool). To do so you can force a suspending function to execute its body within a particular thread pool, for example:

suspend fun getAuthenticationStatus(): AuthenticationStatus = withContext(Dispatchers.IO) {
    when (val result = repository.getAuthenticationStatus()) {
        is Result.Success -> result.data
        is Result.Error -> AuthenticationStatus.Unauthorized
    }
}

This way, even if you're calling this suspending function from a UI scope (MainScope), you are 100% sure that the suspending function is executed in a worker thread and you can update the UI with the returned result in the main thread, such as:

MainScope().launch {
            userIdentityVM.getAuthenticationStatus().run {
                when (this) {
                    is AuthenticationStatus.Authenticated -> {
                       // do something
                    }
                    is AuthenticationStatus.Unauthorized -> {
                       // do something else
                    }
                }
            }
        }

To sum up, by using withContext you can make your suspending function "Main Safe".

The difference between scope and context is basically the intended purpose. To launch a coroutine you normally use launch coroutine builder, defined as an extension function on CoroutineScope.

fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    // ...
): Job

The context specified as parameter on the coroutine scope is merged to coroutine scope by plus operator and takes precedence on the "default" context specified by coroutine scope. This way you can execute the code in a "parent" context. To go deep I suggest you this article by Roman Elizarov (Team Lead for Kotlin libraries @JetBrains).

like image 1
Nicola Gallazzi Avatar answered Oct 22 '22 17:10

Nicola Gallazzi