Day88 of #100DaysOfCode

Kushagra Kesav
4 min readMay 5, 2022

--

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

Coroutines in Kotlin

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.DateTimeFormatter
val 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!

--

--

Kushagra Kesav
Kushagra Kesav

Written by Kushagra Kesav

Developer Relations | Communities | Software Engineering | https://www.linkedin.com/in/kushagrakesav

No responses yet