Dispatchers in Kotlin Coroutines

In this post, we will talk about dispatchers, we will learn what is dispatchers in Kotlin Coroutines? How it works in Coroutines. will learn all type of dispatchers that is available in Coroutine.

What is Dispatchers in Coroutines

A dispatcher basically determines which thread or thread pool the coroutine runs on. So you don’t necessarily need to define a dispatcher if you don’t want to. You can run coroutine very easily like in previous blog we’ve done so far, without specifying which dispatcher we want. However, if you want to bit more precise on how your coroutine runs, you can specify a dispatcher. In android, the application dispatcher plays an important role to manage different types of tasks.

Types of Dispatchers in Coroutines

Majorly 4 different types of dispatchers are available in coroutines, depending on the task specificity. So some dispatchers are more suitable for network communications, others are for processing a large amount of data, some available for communicating with UI, and so on.

Basically, you decide which dispatchers your coroutines will run based on what the coroutines actually do.

So we have few common dispatchers in Coroutine, that we use. I have listed them here, so that we can see which dispatcher is for which kind of task.

  • Main Dispatchers: we will use the Main dispatcher when we want to update the UI
  • Default Dispatchers: Useful for performing CPU intensive work
  • IO Dispatchers: Useful for network communication or reading/writing files
  • Unconfined Dispatchers: Starts the coroutine in the inherited dispatcher that called it
  • newSingleThreadContext(“MyThread”) – Forces creation of a new thread

How we can use Dispatchers

Basically we have to pass a parameter to the launch function like below

   launch(Dispatchers.Default) {
	// do some task here
   }

1. Main Dispatchers

Dispatchers.Main is for updating UI driven applications. So for example, android app is very UI focused. So we will use the main dispatchers when we want to update UI. You can say, we will use the Main Dispatcher when we want to update the UI. You guys already know that the UI cannot be updated unless you are on UI thread, which means the main thread.

So let’s go ahead and take an example for better understanding. Let’s open the Android Studio and create a new project. Once the project is synced then add the Coroutines in app/build.gradle

    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"

Now paste the below code inside the onCreate() method

package com.coroutineskotlinexample

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*

class MainActivity : AppCompatActivity() {
    companion object {
        private const val TAG = "MainActivity"
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        runBlocking {
            launch(Dispatchers.Main) {
                Log.d(TAG, "Main dispatcher. Thread: ${Thread.currentThread().name}")
            }
        }
    }
}

As you saw, in the above code block I launch coroutines in our Main Dispatchers. Inside the block we log the name of the thread, just to see which thread on.

2. Default Dispatchers

Default dispatchers is very useful for CPU intensive work. So things like data processing, image processing. That kinds of work should usually be done with the default dispatchers.

package com.coroutineskotlinexample

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

class MainActivity : AppCompatActivity() {
    companion object {
        private const val TAG = "MainActivity"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        runBlocking {
            launch(Dispatchers.Default) {
                Log.i(MainActivity.TAG, "Default. Thread: ${Thread.currentThread().name}")
            }
        }
    }
}

Let run the above code, and the logcat, Here as you see the default dispatchers is running on a worker thread. the output is below

 I/MainActivity: Default. Thread: DefaultDispatcher-worker-1

3. IO Dispatchers

This is very useful for network communication or reading/writing files. So any Input Output operations should be go through this IO dispatchers.

import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

class MainActivity : AppCompatActivity() {
    companion object {
        private const val TAG = "MainActivity"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        runBlocking {
            launch(Dispatchers.IO) {
                Log.i(MainActivity.TAG, "IO. Thread: ${Thread.currentThread().name}")
            }
        }
    }
}  

IO Dispatchers also run in worker thread, So this is kind of internal to Coroutines. So we don’t really have too much control. We simply know that, the IO Dispatchers is specific for input/output operations.

Output
I/MainActivity: IO. Thread: DefaultDispatcher-worker-1

4. Unconfined Dispatchers

Unconfined dispatchers start the coroutine in the inherited dispatcher that called it. So if you have a coroutine that works on Main dispatchers and then you start another coroutine as unconfined. It will start the coroutine in the inherited dispatcher which is the main.

So in this case we are not defining what dispatcher we want. We let the system decide for us.

package com.coroutineskotlinexample

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*

class MainActivity : AppCompatActivity() {
    companion object {
        private const val TAG = "MainActivity"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        runBlocking {
//            launch(Dispatchers.Main) {
//                Log.i(TAG, "Main dispatcher. Thread: ${Thread.currentThread().name}")
//            }

            launch(Dispatchers.Unconfined) {
                Log.i(TAG, "Unconfined1. Thread: ${Thread.currentThread().name}")
                delay(100L)
                Log.i(TAG, "Unconfined2. Thread: ${Thread.currentThread().name}")
            }
        }
    }
}
Output
 I/MainActivity: Unconfined1. Thread: main
 I/MainActivity: Unconfined2. Thread: kotlinx.coroutines.DefaultExecutor

let run the code and see. So we can see that Unconfined one is running on main thread. However we delaying for 100 milliseconds, and Unconfined2 is actually running a different thread. So you can see that the Coroutines has shifted from one thread to another thread when it was resumed.

5. newSingleThreadContext(“MyThread”)

Now we have another dispatcher which is a new single thread context. And this simply creates a new thread for us. So It forces the creation of a new thread? This shouldn’t really be used that often because new threads are very expensive, and coroutines are very very lightweight.

So we should try to use coroutines as they are intended. But sometime might want the situation, so its. good to know that you have this possibility.

package com.coroutineskotlinexample

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.launch
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.runBlocking

class MainActivity : AppCompatActivity() {
    companion object {
        private const val TAG = "MainActivity"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        runBlocking {
            launch(newSingleThreadContext("MyThread")) {
                Log.i(
                    MainActivity.TAG,
                    "newSingleThreadContext. Thread: ${Thread.currentThread().name}"
                )
            }
        }
    }
}     
Output
I/MainActivity: newSingleThreadContext. Thread: MyThread

As I previously explained, newSingleThreadContext basically creates a new thread, specifically for our coroutines. So here on logcat we saw that thread is MyThread. Now like said this is a bit expensive in term of resources. So should really use it unless you absolutely have to.

Conclusion

So that how you would use dispatchers to perform operations, based on the type of operation that you have. Let puts all dispatchers in a single programme.

package com.coroutineskotlinexample

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*

class MainActivity : AppCompatActivity() {
    companion object {
        private const val TAG = "MainActivity"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        runBlocking {
            launch(Dispatchers.Main) {
                Log.i(TAG, "Main dispatcher. Thread: ${Thread.currentThread().name}")
            }

            launch(Dispatchers.Unconfined) {
                Log.i(TAG, "Unconfined1. Thread: ${Thread.currentThread().name}")
                delay(100L)
                Log.i(TAG, "Unconfined2. Thread: ${Thread.currentThread().name}")
            }

            launch(Dispatchers.Default) {
                Log.i(TAG, "Default. Thread: ${Thread.currentThread().name}")
            }

            launch(Dispatchers.IO) {
                Log.i(TAG, "IO. Thread: ${Thread.currentThread().name}")
            }

            launch(newSingleThreadContext("MyThread")) {
                Log.i(TAG, "newSingleThreadContext. Thread: ${Thread.currentThread().name}")
            }
        }
    }
}
 
Output
 I/MainActivity: Unconfined1. Thread: main
 I/MainActivity: Default. Thread: DefaultDispatcher-worker-1
 I/MainActivity: IO. Thread: DefaultDispatcher-worker-2
 I/MainActivity: newSingleThreadContext. Thread: MyThread
 I/MainActivity: Unconfined2. Thread: kotlinx.coroutines.DefaultExecutor
Subscribe
Notify of
0 Comments
Inline Feedbacks
View all comments