Day88 of #100DaysOfCode
Hii folks 🙌
Today I will be starting a new unit and pathway in which we will learn the coroutines in Kotlin.
Unit 4: Internet
Pathway 1: Coroutines
https://developer.android.com/courses/android-basics-kotlin/course
Creating and using threads for background tasks directly has its place in Android, but Kotlin also offers Coroutines which provide a more flexible and easier way to manage concurrency.
Coroutines enable multitasking but provide another level of abstraction over simply working with threads. One key feature of coroutines is the ability to store state so that they can be halted and resumed. A coroutine may or may not execute.
The state, represented by continuations, allows portions of code to signal when they need to hand over control or wait for another coroutine to complete its work before resuming. This flow is called cooperative multitasking. Kotlin’s implementation of coroutines adds a number of features to assist multitasking. In addition to continuations, creating a coroutine encompasses that work in a Job
, a cancelable unit of work with a lifecycle, inside a CoroutineScope
. A CoroutineScope
is a context that enforces cancellation and other rules to its children and their children recursively. A Dispatcher
manages which backing thread the coroutine will use for its execution, removing the responsibility of when and where to use a new thread from the developer.
We’ll learn more about these later but Dispatchers
are one of the ways coroutines can be so performant. One avoids the performance cost of initializing new threads.
Let’s adapt our earlier examples to use coroutines.
import kotlinx.coroutines.*fun main() {
repeat(3) {
GlobalScope.launch {
println("Hi from ${Thread.currentThread()}")
}
}
}Hi from Thread[DefaultDispatcher-worker-2@coroutine#2,5,main]
Hi from Thread[DefaultDispatcher-worker-1@coroutine#1,5,main]
Hi from Thread[DefaultDispatcher-worker-1@coroutine#3`,5,main]
The snippet above creates three coroutines in the Global Scope using the default Dispatcher. The GlobalScope
allows any coroutines in it to run as long as the app is running.
The launch()
function creates a coroutine from the enclosed code wrapped in a cancelable Job object. launch()
is used when a return value is not needed outside the confines of the coroutine.
Let’s look at the full signature of launch()
to understand the next important concept in coroutines.
fun CoroutineScope.launch() {
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
}
Behind the scenes, the block of code we passed to launch is marked with the suspend
keyword. Suspend signals that a block of code or function can be paused or resumed.
A word about runBlocking
The next examples will use runBlocking()
, which as the name implies, starts a new coroutine and blocks the current thread until completion. It is mainly used to bridge between blocking and non-blocking code in main functions and tests. We will not be using it often in typical Android code.
import kotlinx.coroutines.*
import java.time.LocalDateTime
import java.time.format.DateTimeFormatterval formatter = DateTimeFormatter.ISO_LOCAL_TIME
val time = { formatter.format(LocalDateTime.now()) }suspend fun getValue(): Double {
println("entering getValue() at ${time()}")
delay(3000)
println("leaving getValue() at ${time()}")
return Math.random()
}fun main() {
runBlocking {
val num1 = getValue()
val num2 = getValue()
println("result of num1 + num2 is ${num1 + num2}")
}
}
getValue()
returns a random number after a set delay time. It uses a DateTimeFormatter
. To illustrate the appropriate entry and exit times. The main function calls getValue()
twice and returns the sum.
entering getValue() at 17:44:52.311
leaving getValue() at 17:44:55.319
entering getValue() at 17:44:55.32
leaving getValue() at 17:44:58.32
result of num1 + num2 is 1.4320332550421415
To see this in action, replace the main()
function (keeping all the other code) with the following.
fun main() {
runBlocking {
val num1 = async { getValue() }
val num2 = async { getValue() }
println("result of num1 + num2 is ${num1.await() + num2.await()}")
}
}
The two calls to getValue()
are independent and don't necessarily need the coroutine to suspend. Kotlin has an async function that's similar to launch. The async()
function is defined as follows.
fun CoroutineScope.async() {
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
}
The async()
function returns a value of type Deferred
. A Deferred
is a cancelable Job
that can hold a reference to a future value. By using a Deferred
, we can still call a function as if it immediately returns a value - a Deferred
just serves as a placeholder, since we can't be certain when an asynchronous task will return. A Deferred
(also called a Promise or Future in other languages) guarantees that a value will be returned to this object at a later time. An asynchronous task, on the other hand, will not block or wait for execution by default. To initiate that the current line of code needs to wait for the output of a Deferred
, we can call await()
on it. It will return the raw value.
entering getValue() at 22:52:25.025
entering getValue() at 22:52:25.03
leaving getValue() at 22:52:28.03
leaving getValue() at 22:52:28.032
result of num1 + num2 is 0.8416379026501276
When to mark functions as suspend
In the previous example, we may have noticed that the getValue()
function is also defined with the suspend
keyword. The reason is that it calls delay()
, which is also a suspend
function. Whenever a function calls another suspend
function, then it should also be a suspend
function.
Not necessarily. The getValue()
function is actually called in the lambda passed into runBlocking()
. The lambda is a suspend
function, similar to the ones passed into launch()
and async()
. However, runBlocking()
itself is not a suspend
function. The getValue()
function is not called in main()
itself, nor is runBlocking()
a suspend
function, so main()
is not marked with suspend
. If a function does not call a suspend
function, then it does not need to be a suspend
function itself.
That is all for Day88 ✅
Thanks for reading, See you tomorrow!