The difference between suspension and blocking of Kotlin coroutines

1. Introduction

Kotlin coroutines introduce a very powerful asynchronous programming model to achieve concurrent operations by suspending instead of blocking. Here is a detailed introduction to Kotlin coroutine hanging and blocking:

  1. Suspending:
    • Suspend means that the execution of a coroutine can be paused and resumed without blocking the thread.
    • The suspend function is a special function that allows a coroutine to suspend and release the thread, allowing other coroutines to run while the coroutine is suspended.
    • Coroutines can be suspended while performing IO operations, waiting for network requests, sleeping, or performing any operation that may cause blocking.
  1. block:
    • Blocking means that a thread is suspended while performing an operation until the operation is completed and cannot perform other tasks.
    • In traditional multi-threaded programming, blocking calls (such as Thread.sleep() or waiting for I/O operations to complete) are usually used, which causes the thread to be blocked and wastes valuable resources.
  1. Non-blocking features of coroutines:
    • Kotlin coroutines make executing multiple tasks on the same thread more efficient by suspending tasks to background threads without blocking the main thread.
    • Since coroutines do not need to occupy threads all the time, a large number of coroutines can be run without creating too many threads.
  1. Use coroutine suspension function:

In Kotlin, suspend functions are declared using the suspend keyword, which allows functions to be suspended within a coroutine.

For example, suspend fun fetchData(): String is a function that can be suspended in a coroutine, which can perform asynchronous operations without blocking the thread.

  1. Coroutine scheduler:
  • The execution of coroutines is managed by the scheduler, which is responsible for deciding when to suspend and resume coroutines, and on which thread to run them.
  • By using different schedulers, you can control how the coroutine executes, such as on the main thread, IO thread, or a custom thread pool.

In summary, the suspension mechanism of Kotlin coroutines allows asynchronous tasks to be performed without blocking threads, which is very useful in writing efficient and responsive concurrent code. The suspend function allows the coroutine to give up the thread while waiting for I/O or performing other operations that may cause blocking to improve the performance and responsiveness of the application.

Two, examples

Here is an example using a Kotlin coroutine that demonstrates the difference between suspending and blocking:

First, make sure that the Kotlin coroutine library has been introduced into your project in order to use coroutines.

kotlinCopy codeimport kotlinx.coroutines.*
import kotlin.system.measureTimeMillis

//A suspension function to simulate network requests
suspend fun fetchData(): String {
    delay(1000) //Simulate a network request delayed by 1 second
    return "Data from the network"
}

fun main() = runBlocking {
    //Create a coroutine scope
    val time = measureTimeMillis {
        val result = async { fetchData() } // Start a coroutine to perform network requests
        println("Waiting for data...")
        println("Data received: ${result.await()}")
    }
    println("Time taken: $time ms")
}

In the above code, we created a suspension function fetchData(), which simulates a network request and uses the delay() function to simulate a delay of 1 second. In the main function, we use runBlocking to create a coroutine scope to execute the coroutine. Then, we use async to start a coroutine to execute the fetchData() function.

Now, let’s look at the difference between suspending and blocking:

  1. Suspension: Use the await() function in async to obtain the result of the network request, but while waiting for the network request, the coroutine will hang without blocking the entire thread. This means that other coroutines can run during this period without wasting thread resources.
  2. Blocking: If we use traditional blocking methods, such as Thread.sleep(1000), the thread will be completely blocked and unable to perform other tasks. This wastes thread resources and reduces application performance.

In short, using the coroutine’s suspension mechanism can achieve non-blocking concurrent operations, improving application performance and resource utilization. The traditional blocking method will waste thread resources and lead to a decrease in the responsiveness of the application.

3. Demonstrate the difference between hanging and blocking through Android projects

Demonstrating hanging and blocking in Android projects is easier to understand

When we create a top-level coroutine through runBlocking, the thread where it is located will be blocked; for example, if we use runBlocking on the main thread to create a coroutine that requires time-consuming operations;

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.ang.rxdemo1.databinding.ActivityCoroutine2Binding
import kotlinx.coroutines.*

class CoroutineActivity2 : AppCompatActivity() {
    lateinit var binding: ActivityCoroutine2Binding;
    private var job: Job? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityCoroutine2Binding.inflate(layoutInflater)
        setContentView(binding.root)


        binding.btnSubmit.setOnClickListener {
            
            runBlocking(Dispatchers.IO + CoroutineName("Top-level coroutine")) {//There are time-consuming operations in the coroutine, which take 10S to complete.
               Log.d(TAG,"Coroutine starts executing")
               delay(1000.times(10))
               Log.d(TAG,"Coroutine execution completed")
            }
            
        }
    }
   

xml layout: activity_coroutine2.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    tools:context=".xiecheng.CoroutineActivity">

    <Button
        android:id="@ + id/btn_submit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="execute"/>

</androidx.appcompat.widget.LinearLayoutCompat>

When the test clicks the “Execute” button multiple times in succession, an ANR will appear after a while, causing the program to crash; this is because the coroutine created by runBlocking blocks the main thread and cannot perform other operations, causing the user to become unresponsive.

If the above code uses the coroutine suspension function to perform time-consuming operations, it will not block the execution of the main thread;

 binding.btnSubmit.setOnClickListener {
// runBlocking(Dispatchers.IO + CoroutineName("Top-level coroutine")) {//There are time-consuming operations in the coroutine, which take 10S to complete.
// Log.d(TAG,"Coroutine starts executing")
// delay(1000.times(10))
// Log.d(TAG,"Coroutine execution completed")
// }
            val coroutineScope = CoroutineScope(Dispatchers.Main + CoroutineName("Coroutine A"))
            coroutineScope.launch{
                Log.d(TAG,"Coroutine starts executing ${Thread.currentThread().name} ${coroutineContext[CoroutineName]?.name}")
                delay(10000)//suspend function, suspend the current coroutine
                Log.d(TAG,"Coroutine execution completed ${Thread.currentThread().name} ${coroutineContext[CoroutineName]?.name}")
            }
        }

Multiple clicks will not block the main line, so ANR exceptions will not occur;

You can also compare the difference between hanging and blocking through the following code

Blocking thread:

 binding.btnSubmit.setOnClickListener {
            Thread.sleep(100000)
            Log.d(TAG,"Coroutine execution completed ${Thread.currentThread().name}")

 }

Suspend a non-blocking thread:

binding.btnSubmit.setOnClickListener {
            GlobalScope.launch(Dispatchers.Main + CoroutineName("Coroutine A")) {
                Log.d(TAG,"Coroutine starts executing ${Thread.currentThread().name} ${coroutineContext[CoroutineName]?.name}")
                delay(10000)//suspend function, suspend the current coroutine
                Log.d(TAG,"Coroutine execution completed ${Thread.currentThread().name} ${coroutineContext[CoroutineName]?.name}")
            }

//Thread.sleep(100000)
// Log.d(TAG,"Coroutine execution completed ${Thread.currentThread().name}")

 }