Kotlin Coroutines: Fundamentals

Feb 14 2024 · Kotlin 1.9, Android 13, Android Studio Giraffe

Part 2: Deep Dive into Coroutines

05. Understand Coroutine Context & Dispatchers

Episode complete

Play next episode

Next
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 04. Challenge: Implement a Coroutine Calling Suspend Function Next episode: 06. Use Coroutine Builders: launch, async, runBlocking

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Transcript: 05. Understand Coroutine Context & Dispatchers

In this episode you’ll learn about the coroutine context and dispatchers.

The coroutine context is a set of Elements, which define the properties of the coroutine.

We normally don’t need to use the CoroutineContext class directly. Instead we concatenate those elements, so they form a context.

The most common element type is the Dispatcher, which defines the thread or threads the coroutine can run on.

Another widely used element is a Job, which is a handle to the coroutine execution.

You can use it to cancel the coroutine, wait for it to finish or to build a parent-children hierarchy of coroutines.

There are also other elements. For example the exception handler or a coroutine name useful for debugging.

In this and the few following episodes you’ll learn about the most important elements.

Like the conductor of an orchestra defines which instrument should play and when, the dispatcher defines the thread or thread pool the coroutine can run on.

There are a few built-in dispatchers, and you can also create your own. Let’s discover a few of them!

Open the starter project in Android Studio and navigate to the DispatchersScreen file.

There several buttons in a column there. Each of them should launch one hundred of coroutines on a different dispatcher.

To launch a coroutine you need some scope. The coroutine scope is a wrapper around the coroutine context. Let’s create one in the startCoroutines() function.

CoroutineScope(dispatcher).launch {
    
}

We want to launch 100 coroutines, so let’s use a repeat() function.

CoroutineScope(dispatcher).launch {
    repeat(100) { index ->
        launch {

        }
    }
}

Finally, add a log message with the thread name and the delay inside the coroutine to be able to discover differences between various dispatchers.

CoroutineScope(dispatcher).launch {
  repeat(100) { index ->
      launch {
          Log.d("Running coroutine #$index", Thread.currentThread().name)
          delay(10.milliseconds)
      }
  }
}

Now, build and run the app.

Click on the Dispatchers button.

Open the logcat pane and add a filter matching the message printed to the log in the startCoroutines() function.

Back inside the app, click the I/O button and look at the LogCat output.

Note the Running coroutine # tags are not in the natural order. That’s because the dispatcher launched them in parallel on different threads.

There can be up to 64 such threads. You can see the thread names contain the DefaultDispatcher-worker- string followed by a number. Each number represents a different thread.

Now, click the “Default” button and look again at the LogCat output.

The output is very similar but not identical as before.

There are also different thread names. B ut, the numbers after the DefaultDispatcher-worker- are smaller than before.

That’s because the Default dispatcher uses the thread pool having a size equal to the number of CPU cores.

Depending on the device you’re running the app on, you’ll see different numbers. Nowadays, the modern smartphone CPUs usually have 8 cores or more.

Clear the LogCat.

Click the “Main” button and look at the LogCat panel.

The output is very different now. The coroutine numbers are in the natural order, and the thread name is always the same.

The Main dispatcher uses the Android UI thread. There is only one such thread per process, so the coroutines run sequentially.

You may change the dispatcher for the part of coroutine without launching a new one. To do that, you can use the withContext() function.

Clear the LogCat once again.

Scoll down to the startCoroutinesWithContext method.

Add the following code:

CoroutineScope(Dispatchers.IO).launch {
  repeat(100) { index ->
    Log.d("Launching coroutine #$index", Thread.currentThread().name)
    launch {
      delay(10.milliseconds)
      withContext(Dispatchers.Main) {
        Log.d("Running coroutine #$index", Thread.currentThread().name)
      }
    }
  }
}

This code is similar to the one in startCoroutines with the only difference that you use the withContext method to switch to the Main Dispatcher.

Build and run the app.

Click on the Dispatchers button.

Open the LogCat.

Click the “I/O with Main context” button

As you can see in the LogCat, all the logs with the tag Running coroutine # are run on the main thread even though the coroutine was initially launched on the I/O Dispatcher. Launching coroutine # messages come from the worker thread.

So, which dispatcher should you use? Well, it depends on what you want to do.

If you don’t have any special requirements, stick to the Default dispatcher. As the name suggests, it’s the… default one.

It has a fixed thread pool size, and is equal to the number of cores your processor has, and is at least two.

So it is appropriate for CPU intensive tasks.

For the input/output operations, like network requests or database queries, use the IO dispatcher.

Input/output operations are usually blocking. They don’t use the CPU much, but they take a lot of time.

The IO dispatcher uses a larger thread pool, so it can handle more operations at the same time than the default one.

If you want to touch the UI or other Android framework APIs requiring the main thread, you have to use the Main dispatcher.

It is possible to create your own dispatchers using the dedicated threads or thread pools. But, it is out of the scope of this course.

That was a lot of information, isn’t it? But don’t worry, you’ll get used to it in no time! :]

In the next episodes you learn deeper about the rest of the coroutine context elements.