Coroutines -- creating background worker threads

In this lecture, we discuss threads and in particular the use if Coroutines.

Your UI runs on a single thread to interact with the user -- this is the main thread. Therefore all code will run in this thread which might result in poor performance if you have a computationally intensive operation that could be run in another thread; for example, if your code is loading a file over the Internet you UI is completely blocked. This is not smart design.

The solution to this is simple: If you have computationally demanding functions or slow running operations the best solution if to run those tasks asynchronously from the UI threads.

What this lecture will teach you

Demo projects

We have discussed a number of AsyncTask examples in class:

The notes below relate specifically to the CountDownTaskKotlin example, but more generally to all the code examples listed above.

Resources

Some excellent references.

Coroutines

As an alternative to Threads, Coroutines allow us to create asynchronous programs in a very fluent way. It is important to understand that coroutines are not threads. Threads are managed by the Android OS. When working, the OS schedules a certain amount of time for each thread to run. If a thread cannot complete the job in that window, the OS interrupts the thread and switches to another thread. On a single-core processor, only one thread can execute at a time. Now, most smartphones have multi-core CPUs, meaning that multiple threads can run in parallel. For example, a thread can be executing on CPU core 1 and another thread can be executing on CPU core 2. Threads are expensive considering the cost of many OS operations, such as thread scheduling or context switching.

Unlike threads, coroutines are not bound to any particular thread, meaning that a coroutine can start executing in one thread and be moved to a different thread. When one coroutine hits a suspension point, the Kotlin Runtime will find another coroutine to resume. In comparison to threads, coroutines have a small memory footprint. Coroutines are not managed by the Android OS. Instead, they are managed by Kotlin Runtime. Therefore, there is no scheduler overhead.

Conceptually, coroutines are jobs that need to be done whereas threads are the places where the jobs get done. Many coroutines can exist at the same time inside a single thread. One long-running job will block the entire thread but in coroutines.

Coroutines demo app

The demo app include in the lecture includes an activity that starts a Coroutine task to count down from 15, as shown below. Different text is rewritten out to the UI in different thread components of the UI and background thread, as shown below.

To create an app using coroutines, you need to add the Kotlin Coroutines dependencies in the Android project as below:

dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:[current version]"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:
[current version]"
}

Starting a coroutine

You can start a coroutine using CoroutineScope, where you have three options for the context (or scope), in which your code will be executed:

In the code below, we specify that the countDownTask method should be executed in a worker thread.
CoroutineScope(IO).launch{
countDownTask()
}

MyRuns and coroutines

We use coroutines in MyRuns:

For MyRuns3, you can use coroutines to operate a database. Again, if you did this in the main UI thread the user experience would be poor.

For MyRun5, you can use a coroutines to run the activity classifier at the background because it is computationally intensive and you do not want to block the UI thread.

Coroutine code

Now let's look at the code for the coroutine demo app example.

Suspend function

The first snippet of code for doing the count down is shown in countDownTask(), which is invoked on a worker thread. For any function to be executed as coroutines, we use the "suspend" keywork when defining the function. This is perpahs the first time you see the delay() function. Delay() is similar to Thread.sleep() but they are different in that delay() only delays that particular coroutine in a thread. It will not interfere all the other coroutines in that thread. Calling Thread.sleep() will sleep the entire thread (so all the coroutines within that thread). So never call Thread.sleep inside a coroutine.

private suspend fun countDownTask(){
for (i in 15 downTo 0) {
delay(800)
setTextOnMainThread(i.toString())
}
setTextOnMainThread("*DONE*")
}

Since we need to update the UI from the worker thread to show the count, we create another function called setTextOnMainThread to handle all the UI-related operations. In this example, we simply update the TextView.  Note that we switch the scope of code execution to the main UI thread using withContext. You can use CoroutineScope too as they do the same thing.

private suspend fun setTextOnMainThread(input: String){
withContext(Main){
textView.text = input
}
}