avatarElye - A One Eye Developer

Summary

The web content explains the concept of suspend functions in Kotlin coroutines, illustrating how they allow for non-blocking asynchronous operations, and demonstrates their practical use in Android UI development.

Abstract

The article delves into the intricacies of suspend functions, a core feature of Kotlin coroutines, emphasizing their role in enabling asynchronous code execution that doesn't block threads. It clarifies the distinction between blocking and suspending operations, showing that suspended functions can pause and resume execution, allowing other tasks to utilize the thread in the meantime. Through diagrams and code examples, the author illustrates how coroutines can run in parallel on the same thread and how they can be used to perform UI updates on Android's main thread without causing jank or freezes. The article also highlights the benefits of using delay() over Thread.sleep() to suspend coroutines without blocking the underlying thread. Finally, the author provides a real-world example of coroutines in action within an Android application, updating multiple UI components concurrently.

Opinions

  • The author suggests that understanding suspend functions is crucial for fully appreciating the capabilities of Kotlin coroutines.
  • The article implies that coroutines provide a more efficient alternative to traditional threading, particularly for I/O-bound and latency-sensitive operations.
  • The author expresses that the use of delay() in coroutines is superior to Thread.sleep() because it doesn't block the thread, allowing for better resource utilization.
  • The author demonstrates enthusiasm for coroutines' ability to update the Android UI in parallel on the main thread, which simplifies concurrency and improves performance.
  • By providing a GitHub repository with example code, the author encourages readers to explore and experiment with coroutines in Android development.
  • The author's use of diagrams and step-by-step explanations conveys a commitment to making complex concepts more accessible to developers.
  • The article concludes with an invitation for readers to follow the author on various platforms for ongoing insights into mobile development and related topics, indicating a desire to foster a community of learning and collaboration.

Understanding suspend function of Kotlin Coroutines

When we talk about coroutine, Suspend Functions are like its backbone. So it is very important to know what it is before one could really appreciate coroutines in full.

However, to understanding what Suspend Functions is, even after reading from various writing found on the internet, it not that straightforward, especially how could it not block the Thread? How is coroutine really different from Thread?

In the Kotlin official coroutines documentation, it is stated

Basically, coroutines are computations that can be suspended without blocking a thread

Suspended = stop and continue? That sounds like blocking to me!

Hunting to know BLOCKING vs SUSPENDING

I search for blocking vs suspending and found it in English Language Site

A process is blocked when there is some external reason that it can not be restarted, e.g., an I/O device is unavailable, or a semaphore file is locked. A process is suspended means that the OS has stopped executing it, but that could just be for time-slicing (multitasking). There is no implication that the process can not be resumed immediately.

Neither of these words, especially blocked, are being used the same as in non-computer contexts.

Wow, very computer science English Term.

But it is helpful. This provides some cue to it. There’s something about time-slicing and resume immediately.

Illustrating in Diagram

From the cue above, I could imagine the difference between Blocking and Suspending as below

BLOCKING: Function A has to be completed before Function B continues. The thread is locked for Function A to complete its execution.
SUSPENDING: Function A, while has started, could be suspended, and let Function B execute, then only resume later. The thread is not locked by Function A.

In short, the suspend function is a function that could be started, paused and resume, (and pause and resume…. if wanted repeatedly) and then end.

Let’s talk code, launch vs thread…

Talking about coroutines without code is so intangible. Let’s get into it.

launch as thread

Let’s start with a simple example

fun testRunFunction() {
    // Start a coroutine
    launch {
        println("In start : ${getThreadName()}")
        Thread.sleep(200)
        println("In ended : ${getThreadName()}")
    }

    run {
        println("Out start: ${getThreadName()}")
        Thread.sleep(300)
        println("Out ended: ${getThreadName()}")
    }
}

The result as below.

Out start: main
In start : ForkJoinPool.commonPool-worker-1
In ended : ForkJoinPool.commonPool-worker-1
Out ended: main

Great, could do threading… … But wait, can’t I just use Thread{} to do so, since it is launched in a different Thread (i.e. ForkJoinPool.commonPool-worker-1) anyway. Where’s the suspend functionality? So what’s so special about launch?

Forcing launch to run on the same thread

But wait…. didn’t I just draw my suspension function illustration above showing SINGLE THREAD? If I could force launch to run on the same thread, maybe we could see something.

Found there’s a way to do so, by putting it in runBlocking and add coroutineContext as parameter to launch

fun testRunFunction() {
    runBlocking {
        // Start a coroutine
        launch(coroutineContext) {
            println("In start : ${getThreadName()}")
            Thread.sleep(200)
            println("In ended : ${getThreadName()}")
        }

        run {
            println("Out start: ${getThreadName()}")
            Thread.sleep(300)
            println("Out ended: ${getThreadName()}")
        }
    }
}

Let’s look at the result

Out start: main
Out ended: main
In start : main
In ended : main

Nice, all are run on main thread!

Hold on… but the In is run after Out. Why?

Well the reason is launch is suspended until the run is completed.

In the run block, there are no non-blocking functions that allow launch to start its work. There’s now at least some suspension functionality seen.

But, it doesn’t seem very useful if all it does can just be done right after the caller function completed. Let check out further…

Replace sleep with delay

Well, let’s use the special function introduced in Kotlin, i.e. delay() to replace Thread.sleep().

fun testRunFunction() {
    runBlocking {
        // Start a coroutine
        launch(coroutineContext) {
            println("In start : ${getThreadName()}")
            delay(200)
            println("In ended : ${getThreadName()}")
        }

        run {
            println("Out start: ${getThreadName()}")
            delay(300)
            println("Out ended: ${getThreadName()}")
        }
    }
}

The result as below

Out start: main
In start : main
In ended : main
Out ended: main

This looks much more interesting now, as In and Out is both mixed up the result. Let’s understand what’s going on with the below diagram.

From the diagram, we could clearly see that the use of delay() doesn’t block the Thread, but release the Thread for the other coroutine to continue its work, and regain it back as the Thread is released.

This now clearly exhibits what was mentioned here.

We are using the delay() function that's like Thread.sleep(), but better: it doesn't block a thread, but only suspends the coroutine itself. The thread is returned to the pool while the coroutine is waiting, and when the waiting is done, the coroutine resumes on a free thread in the pool.

Hopefully, this makes it crystal clear the meaning of the suspend function that is not blocking the Thread.

Launching on Android UI Thread

As we know we could launch on the same Thread and run things in parallel, why not let’s try it on the Android Main UI Thread, updating some UI in Parallel.

I wrote a simple App that update 3 different color status bar with some incremental random number, and race to see who reach the final first

The gist of the code as below

private fun startUpdate() {
    resetRun()

    greenJob = launch(Android) {
        startRunning(progressBarGreen)
    }

    redJob = launch(Android) {
        startRunning(progressBarRed)
    }

    blueJob =launch(Android) {
        startRunning(progressBarBlue)
    }
}

private suspend fun startRunning(
           progressBar: RoundCornerProgressBar) {
    progressBar.progress = 0f
    while (progressBar.progress < 1000 && !raceEnd) {
        delay(10)
        progressBar.progress += (1..10).random()
    }
    if (!raceEnd) {
        raceEnd = true
        Toast.makeText(this, "${progressBar.tooltipText} won!", 
              Toast.LENGTH_SHORT).show()
    }
}

Here you could see there are three jobs got launched. And all calling the same function to update their respective progress bar. The bar got updated in a seemingly parallel fashion. All done in the Main UI Thread without spawning other threads. Amazing!

Check out the code in

For further reading, refers to below.

Thanks for reading. You can check out my other topics here.

You can follow me on Medium, Twitter, Facebook, and Reddit for little tips and learning on mobile development, medium writing, etc related topics. ~Elye~

Android
Android App Development
Kotlin
Mobile App Development
Software Development
Recommended from ReadMedium