Kotlin Coroutines [Part-3]

Bharath Kandula
Tilicho Labs
4 min readFeb 18, 2022

--

This blog is a part of Kotlin Coroutines series. If you want to check Part-2:

Coroutine context and dispatchers

In this, we’ll learn about the CoroutineContext and then continue with dispatchers as one of the important elements of the CoroutineContext.

Each coroutine has its own CoroutineScope and CoroutineContext. CoroutineContext can be from parent to child, the child inherits the properties of parent coroutine.

Dispatchers :

It decides, on which thread coroutine executes. We can also assign the name of the coroutine.
Similar to CoroutineScope with every coroutine there exists a coroutineContext.

Types of Dispatchers

  1. Default
  2. Confined
  3. UnConfined
  4. Main
  5. IO

Additional types

  1. Passing coroutineContext
  2. Custom thread

Coroutine without parameter [Confined]

// Without Parameter : CONFINED [CONFINED Dispatcher]
runBlocking { //Main thread
launch {
println("C1 : ${Thread.currentThread().name}") //Thread:main
delay(1000)
println("C1 after delay: ${Thread.currentThread().name}") // Thread : main
}
}

It is going to inherit the coroutineContext from the immediate parent, which is runBlocking, it executes in the main thread.

Dispatcher Default

//With Parameter : DEFAULT [DEFAULT Dispatcher]
runBlocking {
launch(Dispatchers.Default) {
println("C2 : ${Thread.currentThread().name}") // Thread :T1
delay(100)
println("C2 after delay: ${Thread.currentThread().name}") // Thread :same thread (T1) or some other thread
}
}

It creates a coroutine at the application level

It executes in separate background thread

After executing the suspending function such as delay, in some cases T1 is released.
After the delay statement is executed, it either executes on T1 or even executes on some other thread.
Dispatcher Default is good for anything that is CPU intensive.

Thread may change after execute of suspending function.

Dispatcher unconfined

// With Parameter : UNCONFINED [UNCONFINED Dispatcher]

launch(Dispatchers.Unconfined) {
println("C3 : ${Thread.currentThread().name}") // Thread :main
delay(100)
println("C3 after delay: ${Thread.currentThread().name}") // Thread :some other thread
}

This Coroutine is going to inherit the coroutineContext from the immediate parent of runBlocking.

c3 is on the main thread, after delay function, which is suspending function.

c3 after delay is going to execute on some other thread not main

Which will be assigned by a shared pool of thread.

Dispatcher.Main and IO

  • Dispatcher.Main is used to update UI elements.
  • Dispatcher.IO is a user to perform I/O operations like API calls

Code sample

Add the below dependencies.

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'

Inside activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<Button
android:id="@+id/start_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dp"
android:padding="20dp"
android:text="Start" />

<TextView
android:id="@+id/tv_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="0"
android:textColor="@color/black"
android:textSize="40sp" />

</RelativeLayout>

In the above XML, added the simple button to start/perform the operations and add textView for updating UI using Dispatcher.Main.

Inside MainActivity.kt:

import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*

lateinit var textView: TextView
lateinit var startButton: Button

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView = findViewById(R.id.tv_textview)
startButton = findViewById(R.id.start_button)

startButton.setOnClickListener {
runBlocking {
incText()
makeNetWorkRequest()
}
}
}

private suspend fun incText() {
var num = 0
GlobalScope.launch {
Log.i("Main", Thread.currentThread().name)
repeat(5) {
delay(1500)
++num
withContext(Dispatchers.Main) {
textView.text = num.toString()
}
}
textView.text = "Done!"

}
}

private suspend fun makeNetworkRequest() {
GlobalScope.launch(Dispatchers.IO) {
Log.i("Main", "Please wait...")
delay(1000)
Log.i("Main", "Making network request...")
delay(1000)
Log.i("Main", "Network request complete!")
withContext(Dispatchers.Main) {
Toast.makeText(applicationContext, "NetWorkRequest sucess", Toast.LENGTH_SHORT)
.show()
}
}
}

}

In the above code, makeNetworkRequest() function performs the network request in Dispatchers.IO , after completion of network request showing a Toast, the is triggered in Dispatchers.Main

Passing coroutineContext

runBlocking{
//Using coroutineContext property flow context from parent to child
launch(coroutineContext) {
println("C4 : ${Thread.currentThread().name}") // Thread : main
delay(1000)
println("C4 after delay: ${Thread.currentThread().name}") // Thread : main
}
}

In this we pass coroutineContext, this coroutineContext is basically the property of runBlocking.

This property is part of runBlocking coroutine, which is the immediate parent.

when we pass a coroutineContext as a parameter to the child coroutine of launch then it will basically pass parent to the child.

The context of runBlocking will be inherited by this launch coroutine.

It executes on main thread ,like CONFINED

launch(Dispatchers.Unconfined) {
println("C5 : ${Thread.currentThread().name}") // Thread :main
delay(1000)
println("C5 after delay: ${Thread.currentThread().name}") // Thread :some other thread

launch(coroutineContext) {
println("C6 : ${Thread.currentThread().name}") // Thread : some other thread
delay(1000)
println("C6 after delay: ${Thread.currentThread().name}") // Thread : some other thread
}
}

Custom thread

launch(newSingleThreadContext("Custom name")) {
println("newSingleThreadContext thread : ${Thread.currentThread().name}") // Thread : some other thread

}

This actually helps you create your dedicated thread, need to make sure to release it and handle it correctly when it is no longer needed.

Because creating your own thread is a very expensive resource.

Code sample

import kotlinx.coroutines.*


fun main() = runBlocking { // Thread :main

/*
*
* Without Parameter : CONFINED [CONFINED Dispatcher]
*
*/

launch {
println("C1 : ${Thread.currentThread().name}") // Thread : main
delay(1000)
println("C1 after delay: ${Thread.currentThread().name}") // Thread : main
}
/*
*
* With Parameter : DEFAULT [DEFAULT Dispatcher]
*
*/

launch(Dispatchers.Default) {
println("C2 Dispatchers.Default: ${Thread.currentThread().name}") // Thread : T1
delay(100)
println("C2 after delay Dispatchers.Default : ${Thread.currentThread().name}") // Thread :same thread (T1) or some other thread
}

/*
*
* With Parameter : UNCONFINED [UNCONFINED Dispatcher]
*
*/

launch(Dispatchers.Unconfined) {
println("C3 Unconfined: ${Thread.currentThread().name}") // Thread :main
delay(100)
println("C3 after delay Unconfined: ${Thread.currentThread().name}") // Thread :some other thread
}

/*
*
* Using coroutineContext property flow context from parent to child
*
*/

launch(coroutineContext) {
println("C4 : ${Thread.currentThread().name}") // Thread : main
delay(1000)
println("C4 after delay: ${Thread.currentThread().name}") // Thread : main
}

launch(Dispatchers.Unconfined) {


println("C5 : ${Thread.currentThread().name}") // Thread :main
delay(1000)
println("C5 after delay: ${Thread.currentThread().name}") // Thread :some other thread

launch(coroutineContext) {
println("C6 : ${Thread.currentThread().name}") // Thread : some other thread
delay(1000)
println("C6 after delay: ${Thread.currentThread().name}") // Thread : some other thread
}
}

launch(newSingleThreadContext("Custom name")) {
println("newSingleThreadContext thread : ${Thread.currentThread().name}")
}

print("\n.....................\n")
}

References

--

--