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?
GlobalScope.launch(Dispatchers.IO) {}
GlobalScope.launch{}
CoroutineScope(Dispatchers.IO).launch{}
lifecycleScope.launch(Dispatchers.IO){}
lifecycleScope.launch{}
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.
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.
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).
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).
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:
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 thatDispatchers.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 CoroutineContext
s 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() } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With