A coroutine is a concurrency design pattern that you may use on Android to make asynchronous programming easier to understand. Coroutines are used in Android to manage long-running operations that would otherwise block the main thread and make your app unusable.
- A coroutine can be compared to a light-weight thread. Coroutines, like threads, can execute in parallel, interact, and wait for each other. The most significant distinction is that coroutines are comparatively cheap, nearly free, we may build thousands of them for very little cost in terms of performance. True threads, on the other hand, are costly to begin and maintain. A thousand threads can be difficult for a modern system to handle.
- As a result, coroutines can be defined as lightweight threads. Coroutines make it simple to program in both synchronous and asynchronous modes. Coroutines are ideally suited for executing non-blocking activities in multithreading since they allow execution to be halted and resumed at a later time.
Contents
Coroutines dependency
dependencies { ... implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9") }
Features of Kotlin Coroutines
On Android, coroutines are the recommended solution for asynchronous programming. The following are some of the most notable characteristics of coroutines.
- Lightweight: Because of the support for suspension, which does not block the thread where the coroutine is executing, several coroutines can be run on a single thread. Suspending, rather than blocking, frees up memory while allowing many actions to run at the same time.
- Built-in cancellation support: The running coroutine hierarchy generates cancellation automatically.
- Fewer memory leaks: It leverages structured concurrency to conduct operations within a scope, resulting in fewer memory leaks.
Need of Kotlin Coroutines
As we all know, all of the components in an Android application run on the same thread by default, which is our Main Thread. Because the application is single-threaded, the main thread has a lot of responsibilities, such as rendering views, executing logical code in sequential order, and so on. It was therefore our obligation to ensure that we did not obstruct the primary thread. In some circumstances, such as executing network operations or significant logical operations, a multi-threaded technique is required. We shouldn’t hold the main thread until these long or short running jobs are completed because it causes the app to freeze and become unusable, so to prevent this we use Kotlin coroutines.
Some important concepts related to coroutines are:
- suspend
Suspend is nothing more than a term. A suspend function is just that: a function that can be interrupted and resumed at a later time. We can perform long-running processes and wait for them to finish without having to block. Suspending functions use the same syntax as conventional functions, but with the addition of the suspend keyword.
suspend fun example() { ... }
Important point: A good rule of thumb is that the suspend function should only be invoked from a coroutine or another suspension function.
- Job
Jobs can be organized into parent-child hierarchies, with the cancellation of one parent causing the cancellation of all of its offspring in a recursive manner. When a child fails with an exception other than CancellationException, its parent and, as a result, all of its other children are canceled.
Each coroutine created using launch or async provides a Job object that uniquely identifies and maintains the coroutine’s lifecycle. When we use a standard Job instance and any child other than CancellationException throws an exception, it cancels the parent as well as the other children. We have a SupervisorJob to help us deal with this situation.
// private val superJob = lazy { SupervisorJob() } private val job = lazy { Job() }
Depending on our needs, we may also pass a parent Job as a parameter when constructing a SupervisorJob instance. Children with a supervisory position have the potential to fail independently of one another. A failure or cancellation of any kid does not result in the supervisor’s job failing, and it has no effect on the supervisor’s other children, so a supervisor can employ unique logic to deal with its children’s failures.
A Job can also be passed to a CoroutineScope, which will manage its lifecycle.
class Example { fun examplemethod() { //We can control the lifecycle of the coroutine if we have a handle on it. val job = scope.launch(Dispatchers.IO) {} if (true) { job.cancel() } } }
The statement job.cancel() just cancels the coroutine started above, this doesn’t affect the scope.
Important point: A job is in the active state while the coroutine is running, until it is completed, fails, or is canceled.
- Dispatchers
Dispatchers provide the thread on which the operation shall be carried out. In Rx, dispatchers are simply schedulers. In Android, we mainly have three dispatchers
Dispatchers. Main: A coroutine dispatcher that only works with UI items in the Main thread. You can use this dispatcher directly or through the MainScope factory. Typically, a single-threaded dispatcher is used.
If no main thread dispatchers are available in the classpath, access to this property may result in an IllegalStateException.
Dispatchers. Default: The default CoroutineDispatcher is used by all coroutine builders such as launch, async, and so on if no dispatcher or other ContinuationInterceptor is specified in their context. It is optimized for CPU-intensive work that is not performed on the main thread.
Dispatchers.IO: The CoroutineDispatcher is a thread pool and networking offloader for blocking IO jobs. This pool’s threads are created and deactivated as needed.
withContext
We use withContext to switch between Dispatchers within a coroutine. It’s nothing more than a suspend function. It invokes the specified suspending block with the specified coroutine context, suspends until it completes, and returns the result. This suspending function can be turned off. It checks for cancellation of the resulting context immediately and throws CancellationException if it is not active.
// withContext(Dispatchers.Main)
- CoroutineScope
A CoroutineScope keeps track of any coroutine it generates with the launch or async builder functions. It gives you the option of canceling a coroutine at any time. The range is limited to a single lifetime. Without a scope, a coroutine can’t be started. Whenever a failure occurs, CoroutineScope is alerted.
The KTX libraries for Android provide a CoroutineScope in connection to lifecycle classes like viewModelScope and lifecycle scope. To access these custom scopes, we need to add the dependency lifecycle-viewmodel-ktx to the module-level build.gradle.
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
Coroutine builders are used to create coroutines.
Coroutine builders are simple extension methods that allow you to create and run coroutines. Because suspending functions cannot be accessible from the normal world, they serve as a link between the two worlds. As Because builders are not of the type suspend, they can be used in normal functions.
It’s really easy to make a coroutine.
Let’s have a look at how to achieve it with these tools.
- launch
The simplest way to create a coroutine is by calling the launch builder on a specified scope. It creates a new coroutine without interrupting the current thread and returns a Job reference to it. When the resulting job is cancelled, the coroutine is cancelled as well. There is no response from the launch. It’s utilised in “fire and forget” scenarios, where we start working on a new thread but don’t care about the outcome.
scope.launch(Dispatchers.IO){ //New Coroutine is created example() }
- async
We can start a new coroutine with the async builder, and it will return a result with a suspend function call await.Async’s return type is deffered, which is analogous to Java’s future and JavaScript’s promise. In a regular method, we can’t utilise async because it needs to use the suspend function await to retrieve the result. As a result, we usually utilise launch builder inside a normal function, followed by async. We should only use async when we need multiple tasks to run in parallel, as it waits for all of them to finish before returning a postponed result.
suspend fun getUser(): User = viewModelScope{ val result = async(Dispatchers.IO) { fetchUserData() } result.await() }
- runBlocking
Until the coroutine is finished, runBlocking blocks the current thread on which it is invoked. It executes the coroutine in the context of the thread from which it was called.
InterruptedException is thrown. If the thread that has been blocked is interrupted. Let’s have a look at a code snippet using runBlocking.
fun exampleRunBlocking(){ println(" 1 statement of runBlocking") runBlocking { delay(3000L) println("2 statement of runBlocking") } println("3 statement of runBlocking") }
Difference between Kotlin Coroutines and Threads
- It takes a long time to fetch data from one thread and transmit it to another. It also introduces a lot of callbacks, which makes the code less readable. Coroutines, on the other hand, do away with callbacks.
- Constructing and halting a thread is a costly task since it necessitates the creation of their own stacks, however creating coroutines is fairly inexpensive when compared to the performance it provides. Coroutines do not have a stack of their own.
- Coroutines are suspendable, whereas threads are blocking. While a thread sleeps for a period of time, the entire thread becomes stopped, and it is unable to execute any other operations; however, because coroutines are suspendable, they can perform any other work when they are delayed for a few seconds.
- When opposed to threads, coroutines provide a far higher level of concurrency because many threads require blocking and context switching. Context switching with threads is slower than with coroutines because threads can only switch context when the job of one thread is completed, whereas coroutines can change context at any time because they are suspendable.
- Coroutines are light and quick. Threads are slower than coroutines because threads are controlled by the operating system, but coroutines are maintained by the user. Having thousands of coroutines collaborate is far superior to having tens of threads collaborate.
We hope that this guide will assist you in understanding all about the concepts of Kotlin coroutine. We have concentrated on making a basic, meaningful and easy-to -learn guide to the concepts of it with suitable example. Still if you have any problems regarding it, please post them in the comments section, we will be glad to assist you.
You might also like:
Android Architecture Pattern (MVVM, MVC, MVP)