Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GlobalScope vs CoroutineScope vs lifecycleScope

I am used to working with AsyncTask and understand it pretty well due to its simplicity. But Coroutines are confusing to me. Can you please explain to me in a simple way what is the difference and purpose of each of the following?

  1. GlobalScope.launch(Dispatchers.IO) {}
  2. GlobalScope.launch{}
  3. CoroutineScope(Dispatchers.IO).launch{}
  4. lifecycleScope.launch(Dispatchers.IO){}
  5. lifecycleScope.launch{}
like image 214
Dim Avatar asked Nov 25 '20 16:11

Dim


People also ask

What is GlobalScope and CoroutineScope?

A global CoroutineScope not bound to any job. Global scope is used to launch top-level coroutines which are operating on the whole application lifetime and are not cancelled prematurely. Active coroutines launched in GlobalScope do not keep the process alive. They are like daemon threads.

What is a LifecycleScope?

A LifecycleScope is defined for each Lifecycle object. Any coroutine launched in this scope is canceled when the Lifecycle is destroyed. You can access the CoroutineScope of the Lifecycle either via lifecycle. coroutineScope or lifecycleOwner. lifecycleScope properties.

How do you make a CoroutineScope?

The easiest way to create a coroutine scope object is by using the CoroutineScope factory function 1. It creates a scope with provided context (and an additional Job for structured concurrency if no job is already part of the context).

What is coroutine scope and how is that different from coroutine context?

The difference between a context and a scope is in their intended purpose. It is defined as extension function on CoroutineScope and takes a CoroutineContext as parameter, so it actually takes two coroutine contexts (since a scope is just a reference to a context).


1 Answers

First, let's start with definitions to make it clear. If you need a tutorial or playground for Coroutines and Coroutines Flow you can check out this tutorial/playground i created.

Scope is object you use to launch coroutines that only contains one object which is CoroutineContext

public interface CoroutineScope {     /**      * The context of this scope.      * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.      * Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.      *      * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.      */     public val coroutineContext: CoroutineContext } 

The coroutine context is a set of rules and configurations that define how the coroutine will be executed. Under the hood, it’s a kind of map, with a set of possible keys and values.

Coroutine context is immutable, but you can add elements to a context using plus operator, just like you add elements to a set, producing a new context instance

The set of elements that define the behavior of a coroutine are:

  • CoroutineDispatcher — dispatches work to the appropriate thread.
  • Job — controls the lifecycle of the coroutine.
  • CoroutineName — name of the coroutine, useful for debugging.
  • CoroutineExceptionHandler — handles uncaught exceptions

Dispatchers Dispatchers determine which thread pool should be used. Dispatchers class is also CoroutineContext which can be added to CoroutineContext

  • Dispatchers.Default: CPU-intensive work, such as sorting large lists, doing complex calculations and similar. A shared pool of threads on the JVM backs it.

  • Dispatchers.IO: networking or reading and writing from files. In short – any input and output, as the name states

  • Dispatchers.Main: mandatory dispatcher for performing UI-related events in Android's main or UI thread.

For example, showing lists in a RecyclerView, updating Views and so on.

You can check out Android's official documents for more info on dispatchers.

Edit Even though official document states that

Dispatchers.IO - This dispatcher is optimized to perform disk or network I/O outside of the main thread. Examples include using the Room component, reading from or writing to files, and running any network operations.

Answer from Marko Topolnic

IO runs the coroutine on a special, flexible thread pool. It exists only as a workaround when you are forced to use a legacy, blocking IO API that would block its calling thread.

might be right either.

Job A coroutine itself is represented by a Job. A Job is a handle to a coroutine. For every coroutine that you create (by launch or async), it returns a Job instance that uniquely identifies the coroutine and manages its lifecycle. You can also pass a Job to a CoroutineScope to keep a handle on its lifecycle.

It is responsible for coroutine’s lifecycle, cancellation, and parent-child relations. A current job can be retrieved from a current coroutine’s context: A Job can go through a set of states: New, Active, Completing, Completed, Cancelling and Cancelled. while we don’t have access to the states themselves, we can access properties of a Job: isActive, isCancelled and isCompleted.

CoroutineScope It is defined a simple factory function that takes CoroutineContexts as arguments to create wrapper around the combined CoroutineContext as

public fun CoroutineScope(context: CoroutineContext): CoroutineScope =     ContextScope(if (context[Job] != null) context else context + Job())  internal class ContextScope(context: CoroutineContext) : CoroutineScope {     override val coroutineContext: CoroutineContext = context     // CoroutineScope is used intentionally for user-friendly representation     override fun toString(): String = "CoroutineScope(coroutineContext=$coroutineContext)" } 

and creates a Job element if the provide context does not have one already.

Let's look at GlobalScope source code

/**  * A global [CoroutineScope] not bound to any job.  *  * Global scope is used to launch top-level coroutines which are operating on the whole application lifetime  * and are not cancelled prematurely.  * Another use of the global scope is operators running in [Dispatchers.Unconfined], which don't have any job associated with them.  *  * Application code usually should use an application-defined [CoroutineScope]. Using  * [async][CoroutineScope.async] or [launch][CoroutineScope.launch]  * on the instance of [GlobalScope] is highly discouraged.  *  * Usage of this interface may look like this:  *  * ```  * fun ReceiveChannel<Int>.sqrt(): ReceiveChannel<Double> = GlobalScope.produce(Dispatchers.Unconfined) {  *     for (number in this) {  *         send(Math.sqrt(number))  *     }  * }  * ```  */ public object GlobalScope : CoroutineScope {     /**      * Returns [EmptyCoroutineContext].      */     override val coroutineContext: CoroutineContext         get() = EmptyCoroutineContext } 

As you can see it extends CoroutineScope

1- GlobalScope.launch(Dispatchers.IO) {} GlobalScope is alive as long as you app is alive, if you doing some counting for instance in this scope and rotate your device it will continue the task/process.

GlobalScope.launch(Dispatchers.IO) {}  

runs as long as your app is alive but in IO thread because of using Dispatchers.IO

2- GlobalScope.launch{} It's same as the first one but by default, if you don't have any context, launch uses EmptyCoroutineContext which uses Dispatchers.Default, so only difference is thread with first one.

3- CoroutineScope(Dispatchers.IO).launch{} This one is the same as first one with only syntax difference.

4- lifecycleScope.launch(Dispatchers.IO){} lifecycleScope is an extention for LifeCycleOwner and bound to Actvity or Fragment's lifCycle where scope is canceled when that Activity or Fragment is destroyed.

/**  * [CoroutineScope] tied to this [LifecycleOwner]'s [Lifecycle].  *  * This scope will be cancelled when the [Lifecycle] is destroyed.  *  * This scope is bound to  * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].  */ val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope     get() = lifecycle.coroutineScope 

You can also use this as

class Activity3CoroutineLifecycle : AppCompatActivity(), CoroutineScope {      private lateinit var job: Job      override val coroutineContext: CoroutineContext         get() = job + Dispatchers.Main + CoroutineName("🙄 Activity Scope") + CoroutineExceptionHandler { coroutineContext, throwable ->             println("🤬 Exception $throwable in context:$coroutineContext")         }       private val dataBinding by lazy {         Activity3CoroutineLifecycleBinding.inflate(layoutInflater)     }       override fun onCreate(savedInstanceState: Bundle?) {         super.onCreate(savedInstanceState)         setContentView(dataBinding.root)              job = Job()          dataBinding. button.setOnClickListener {              // This scope lives as long as Application is alive             GlobalScope.launch {                 for (i in 0..300) {                     println("🤪 Global Progress: $i in thread: ${Thread.currentThread().name}, scope: $this")                     delay(300)                 }             }              // This scope is canceled whenever this Activity's onDestroy method is called             launch {                 for (i in 0..300) {                     println("😍 Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this")                     withContext(Dispatchers.Main) {                         dataBinding.tvResult.text = "😍 Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this"                     }                     delay(300)                 }             }         }      }      override fun onDestroy() {         super.onDestroy()         job.cancel()     }  } 
like image 196
Thracian Avatar answered Oct 26 '22 12:10

Thracian