9. Apr 2021Android

Introduction to Kotlin Coroutines

Coroutines are becoming a common technology in mobile application development. Many of us already know this concept or have encountered it. However, we still lack a fundamental understanding of it.

Tomáš ParonaiAndroid Developer

Routine vs Coroutine

It should be clear to each developer what the word routine means. Routine is the basic building block of every program. It is a set of instructions that perform tasks, also known as a function or method. The commands are executed sequentially from top to bottom. In practice, we call this synchronous programming.

fun main() {
    println("main starts")
    routine(1, 500)
    routine(2, 300)
    println("main ends")
}

private fun routine(number: Int, delay: Long) {
    println("Routine $number starts to work")
    Thread.sleep(delay)
    println("Routine $number finished")
}

In this particular example, we can see how the following output proceeds from it. The routine function represents the work that a program does and takes a long delay of ms. The second routine waits 500ms for the first until it finishes. Routine 1 blocks the thread until it ends. Such an instruction is known as blocking.

main starts
Routine 1 starts to work
Routine 1 finished
Routine 2 starts to work
Routine 2 finished
main ends

main starts
Routine 1 starts to work
Routine 1 finished
Routine 2 starts to work
Routine 2 finished
main ends

The example is shown by diagram:

The example is shown by diagrIn the practice, we call non-conventional programming asynchronous. In this case, the instructions of one program are executed in parallel. Asynchronous programming is especially important in Android. Imagine a user waiting in an application for images to download. If images were downloaded on the same thread, running on the entire user interface, the entire user interface would stand still and wait for the instructions to download and save the images to complete. The application would appear to be broken and would lead to a very unpleasant user experience.

The most common solution would be to use a new thread - Thread or RxJava. All of these methods have their advantages and disadvantages. Let's try to show the previous example asynchronously.

Thread

fun main() {
    println("main starts")
    thread { routine(1, 500) }
    thread { routine(2, 300) }
    Thread.sleep(600)
    println("main ends")
}

RxJava

fun main() {
    println("main starts")
    val disposables = CompositeDisposable()
    disposables.addAll(
        Completable.fromAction {
            routine(1, 500)
        }.subscribeOn(Schedulers.newThread()).subscribe(),
        Completable.fromAction {
            routine(2, 300)
        }.subscribeOn(Schedulers.newThread()).subscribe()
    )
    Thread.sleep(600)
    println("main ends")
    disposables.dispose()
}

In both cases we achieve the following output:

main starts
Routine 1 starts to work
Routine 2 starts to work
Routine 2 finished
Routine 1 finished
main ends

main starts
Routine 1 starts to work
Routine 2 starts to work
Routine 2 finished
Routine 1 finished
main ends

As it is clear from the output, both commands ran one after the other, so they did not wait for each other, but ran in parallel. The second routine is faster, so it ended earlier. We call such instructions nonblocking.

With threads, we have no control over new threads. If we imagine that the main is a running Android application and we turn it off after calling the routine, then we get a memory leak.

We do not have this problem with RxJava, because we dispose our instructions at the end of the program, but with the framework itself, it is necessary to remember a large number of instructions that work with multithreading. If we did not call Thread.sleep (600) in the main function, the "main ends" statement would be listed before our routine functions ended, because our routine functions run on a different thread than the main.

Coroutine

From the name coroutine, we can already imagine that this is not a common set of instructions that are executed sequentially.

fun main() = runBlocking {
    println("main starts")
    joinAll(
        async { coroutine(1, 500L) },
        async { coroutine(2, 300L) }
    )
    println("main ends")
}

private suspend fun coroutine(number: Int, t: Long) {
    println("Routine $number starts to work")
    delay(t)
    println("Routine $number finished")

At first sight, it may seem that there are several new unknown commands in the previous image. The command runBlocking blocks the thread on which the command is running and starts a new coroutine. We call runBlocking because the program is executed from top to bottom, if we want to insert a coroutine somewhere and at the same time we want the program not to continue until the coroutine ends. It is one of the constructors for coroutines.

Another construct of coroutine is the launch, which also starts it. I will present you all the constructors and the differences between them in the next article. We insert these coroutines into joinAll, from which the execution of commands does not continue until all coroutines are evaluated. The coroutine function is really the same as the routine from the previous example. We called it conventional. If we run this example, we get the following result.

main starts
Routine 1 starts to work
Routine 2 starts to work
Routine 2 finished
Routine 1 finishedmain 
ends

main starts
Routine 1 starts to work
Routine 2 starts to work
Routine 2 finished
Routine 1 finished
main ends

One of the biggest differences between multithreading and kotlin coroutines is that coroutines run on the same thread on which they were formed. To check the truth of this statement, try to list the name of the thread in the functions  routine  and  coroutine - Thread.currentThread (). Name.

So what is the principle? Creating and managing another thread is hard for memory and CPU. This is what coroutine's work on the diagram looks like.

It makes it easier for us to read the code, because it is not necessary to insert callbacks if we have a return value. In the next article, we will show the simple use of all three methods of asynchronous programming when communicating with a server or a database.

Are you interested in Android? Visit the article on Constraint Layout Helpers

Tomáš ParonaiAndroid Developer