Introduction
Many times one may want to perform long-running tasks, like fetching data from a database or even uploading data, which often take so much time to be completed, what can I do so that even as I perform this long-running task I can continue with other processes? well, coroutines it is.
So!! in this article we'll be talking about coroutine and how we can make use of it. Let's get started...
What is a coroutine, why coroutines, and how to use coroutine?
What is Coroutine?
- Coroutine is an instance of suspendable computation,
- It is similar to thread but its lightweight,
- It is not bound in any thread and it can tie its execution in one thread and continue on the other threads.
Example of a coroutine block of code
fun main() = runBlocking { // this: CoroutineScope
launch { onStop() }
println("Here...there")
}
suspend fun onStop() {
delay(1000L)
println("Stopped!")
}
runBlocking
is a coroutine scope that bridges the non-coroutine world.launch
is a coroutine builder that launches new coroutines with other code concurrentlydelay
is a suspend function that suspends the coroutine for a specified time.Why coroutines: -
- It is less resource intensive as compared to Java threads.
- it prevents the callback problem.
- Easy to maintain and control
Gives a controllable way to handle code
How to use coroutines
fun main() { println("Work started ${Thread.currentThread().name}") //This will print the string with the thread on which it is running. GlobalScope.launch{onStop(name = "1")} CoroutinScope(Dispatcher.Io).launch { onStop("2") } Thread.sleep(4000) } suspend fun onStop(name:String) { delay(1000L) println("Stopped!") }
Jobs
- Jobs are a handle to a coroutine.
- It Models the lifecycle of a coroutine
- Job also acts as the root of the coroutine hierarchy in the coroutine scope
Job Lifecycle
The job lifecycle is usually in the following states:
- New - Created and not started.
- Active - started but not completed
- Completing - waiting for children to complete
- Completed - finished and completed children
- Cancelling - cancel is called or exception is thrown.,
- Cancelled - cancel is completed
These states cannot be accessed directly but rather we use the Properties
that are related to the states.
- isActive - The coroutine is in this state when it has been started, one it is running its operation it moves to the
completing
state - isCompleted - The coroutine is moved to completed once all its children have completed the task.
- isCancelled - The coroutine is in a
canceling
state when a cancel is called or an exception is thrown once the cancel is completed it is moved to isCancelled.
Example
fun main()= runBlocking{
val job = GlobalScope.launch{
var num = 1
while(isActive){
println("The number is:${num++}")
}
}
delay(20)
job.cancel()
}
Coroutine Context
A coroutine context is used to define the behavior of a coroutine and it contains a set of elements
- CoroutinDispatcher which defines
Threading
and by Default it isDispatchers. Default
- a job that defines
lifecycle
and by default it isNo parent job
- CoroutinExceptionHandler by default is
None
- CoroutineName by default is
coroutine
parentContext = Defaults + inherited context + passed arguments
coroutineContext = parentContext + job()
What is context switching
It is to change the context in which a coroutine runs.
the context is switched using the withContext()
Example
fun main() = runBlocking{
doBgTask()
}
suspend fun moveToForground(){
withContext(Dispatchers.Main){
...
}
}
suspend fun doBgTask(){
withContext(Dispatchers.IO){
moveToForground()
}
}
Builder
These are the operations that start the coroutines.
- Act as a 'CoroutineScope' extension function
- Increases control over the lifecycle and behavior of coroutines.
- Returns a job
Type of builder
- launch which is used when there is no result expected.
- await which is used when there is a result expected.
fun main = runBlocking{
val job1 = GlobalScope.launch{
println("Has already started...")
}
val job2 = GlobalScope.async{
println("Starting Here and now....")
delay(1000)
"Okay: Am done!!"
}
println(job1)
println(job2)
println("-".repeat(20))
println(job2.await())
Thread.sleep(2000)
}
Output
Has already started...
"coroutine#2":StandaloneCoroutine{Completed}@71e7a66b
"coroutine#3":DeferredCoroutine{Active}@2ac1fdc4
--------------------
Starting Here and Now...
Okay: Am done!!
CoroutineScope
What does coroutine scope do?
- Defines the scope of a new coroutine.
- keep track of the coroutine
- Give the ability to cancel the ongoing work
- Notifies when a failure happens.
Every coroutine builder is an extension of a coroutine scope and inherits its coroutineContext to automatically propagate all its elements.
What is the best way to obtain an instant of a scope?
CoroutineScope()
- These uses the context provided to it as a parameter and adds a job if one is not provided as part of the contextMainScope()
- usesDispatcher.Main
for its coroutine and has a SupervisorJoblifecycleScope
- Tied to the lifecycle state of the component. Either activity of the fragments.viewModelScope
- Tied to the lifecycle of the viewmodel.
val scope = CoroutinScope(Dispatchers.Main)
// launching a coroutine
val job = scope.launch{
sing()
}
//canceling a coroutine
scope.cancel()
suspend fun sing(){
println("Hallelu...jah.....!!!")
}
Dispatchers
- Coroutine context is a set of elements
- The main element of a coroutine context is the job of the coroutine and the
dispatcher
So what is a Dispatcher?
- Dispatcher is what determines the thread or the threads that a corresponding coroutine uses for its execution.
- it restricts a coroutine execution to a specific thread, dispatches it to a thread, or lets it run unrestricted.
Properties of Dispatchers
Dispatchers. Default
- Is used by all standard builders if no dispatcher is specified in their context.
- It uses a common pool of shared background threads.
- Is considered as the appropriate choice for coroutine computations that consume CPU resources.
Dispatchers.IO
- uses a shared pool of on-demand created threads
- it is designed for offloading of IO-intensive blocking operations
Dispatchers. Unconfined
- This starts coroutine execution in the current call frame until the first suspension that the coroutine builder returns.
- This should not be normally used in code.
Dispatchers. Main
- Used for UI operations and Non-blocking code.
fun main = runBlocking{
withContext(Dispatchers.Main){...}
withContext(Dispatchers.Default){...}
withContext(Dispatchers.IO){...}
withContext(Dispatchers.Unconfined){...}
}
What is context switching?
Context switching is to change the context in which a coroutine runs.
Example
fun main() = runBlocking{ doNetworkStuff() } suspend fun updateUi(){ withContext(Dispatchers.Main){ ... } } suspend fun doNetworkStuff(){ withContext(Dispatchers.IO){ ... updateUi() } }
Conclusion
In conclusion, Kotlin Coroutines have revolutionized the way developers handle asynchronous programming in Android applications. With their simple syntax, lightweight structure, and seamless integration into existing codebases.
Reference