Kotlin – coroutine technology

A coroutine is a lightweight thread.
Threads rely on the scheduling of the operating system to switch between different threads.
Coroutines can switch between different coroutines only at the programming language level, thus greatly improving the operating efficiency of concurrent programming.

The coroutine allows to simulate the effect of multi-threaded programming in single-threaded mode. The suspension and recovery of code execution are completely controlled by the programming language and have nothing to do with the operating system.

Basic usage

First add a dependency library in app/build.gradle

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'

Open coroutine

fun main(){
    GlobalScope. launch {
        println("codes run in coroutine scope")
    }
    Thread. sleep(1000)
}

Run the main function and you will see the printed log

GlobalScope.launch function can create a coroutine scope, so that the code block (Lambda expression) passed to the launch function is run in the coroutine, here it is just printed in the code block A line of log.
But if the Thread.sleep(1000) statement is deleted, it cannot be printed successfully. This is because the Global.launch function creates a top-level coroutine every time, and this kind of coroutine will also end together when the application ends. The reason why the log just now cannot be printed out is because the code in the code block has not had time to run, and the application ends. The Thread.sleep() method makes the main thread block for 1 second, and there is time to print successfully.

runBlocking function

In most cases, it is impossible to calculate the time required by the coroutine every time and then let the main thread block and wait. At this time, you need to use the runBlocking function, which allows the application to end after all the code in the coroutine has run.

fun main(){
    runBlocking{
        println("codes run in coroutine scope")
        delay(1500) //Let the current coroutine run after a specified time delay, which can only be called in the coroutine scope or other suspending functions
        println("codes run in coroutine scope finished")
    }
}

runBlocking function is the sameA coroutine scope will be created, but it can guarantee that the current thread will be blocked until all codes and sub-coroutines in the coroutine scope are executed. It should be noted that the runBlocking function should usually only be used in the test environment, and it is easy to cause some performance problems when used in the formal environment.

Create multiple coroutines

fun main(){
    runBlocking{
        launch {
            println("launch1")
            delay(1000)
            println("launch1 finished")
        }
        launch {
            println("launch2")
            delay(1000)
            println("launch2 finished")
        }
    }
}

Note that the launch function here is different from the GlobalScope.launch function just used.
First of all, it must be called in the scope of the coroutine, and secondly, it will create a sub-coroutine under the scope of the current coroutine. The characteristic of sub-coroutines is that if the coroutine in the outer scope ends, all sub-coroutines under this scope will also end together.
In contrast, GlobalScope.launcThe h function always creates a top-level coroutine, which is similar to a thread, because threads have no hierarchy and are always top-level.

It can be seen that the logs in the two sub-coroutines are printed alternately, indicating that they are indeed running concurrently like multiple threads. However, these two sub-coroutines actually run in the same thread, and it is only up to the programming language to decide how to schedule among multiple coroutines, who is allowed to run and who is suspended. The scheduling process does not require the participation of the operating system at all, which makes the concurrency efficiency of coroutines surprisingly high.

suspend keyword

The

suspend keyword can declare any function as a suspend function, and the suspend functions can call each other.

suspend fun printDot(){
    println(".")
    delay(1000)
}

However, the suspend keyword can only declare a function as a suspend function, and cannot provide it with a coroutine scope. For example, the launch function cannot be called in the printDot() function, because the launch function must be called in the scope of the coroutine

coroutineScope function

The

coroutineScope function is also a suspend function, so it can be called within any other suspend function. Its characteristic is that it will inherit the scope of the external coroutine and create a sub-coroutine. With this feature, you can provide coroutine scope for any suspending function.

suspend fun printDot() = coroutineScope{
    println(".")
    delay(1000)
    launch{ }
}

In addition, the coroutineScope function can also ensure that all codes and sub-coroutines in its scope will be suspended until all the sub-coroutines are executed.

fun main(){
    runBlocking{
        coroutineScope {
            launch {
                for (i in 1..5){
                    println(i)
                    delay(1000)
                }
            }
        }
        println("coroutineScope finished")
    }
    println("run Blocking finished")
}

Note

coroutineScope function will only block the current coroutine, neither affecting other coroutines nor any threads, so it will not cause any performance problems.
The runBlocking function will suspend the external thread, if it happens to be called in the main thread, it may cause the interface to freeze, so it is not recommended to use used in actual projects.

More scope builders

Summary Features

name calling environment features
GlobalScope.launch Can be called anywhere Every time you create a top-level coroutine, it is not commonly used
launch Can only be called in the coroutine scope \
runBlocking Can be called anywhere It will block the thread, it is recommended to use it only in the test environment
coroutineScope can be called in coroutine scope or suspend function \

Commonly used writing methods in projects

When using coroutines, it is also necessary to consider the problem of canceling the coroutines when the user closes the Activity in advance.
Both the GlobalScope.launch function and the launch function will return a Job object, you only need to call the cancel() method of the Job object to cancel the coroutine

val job = GlobalScope.launch{
//Concrete processing logic
}
job. cancel()

Then in actual use, the commonly used writing method is:

val job = Job()
val scope = CoroutineScope(job)
scope. launch {
    //Concrete processing logic
}
job. cancel()

First create a Job object, and then pass it into the CoroutineScope() function, the CoroutineScope() function will return a CoroutineScope object, with CoroutineScope object, you can call its launch function at any time to create a coroutine. Now all coroutines created by calling the launch function of CoroutineScope will be associated under the scope of the Job object. In this way, it only needs to call the cancel() method once to cancel all coroutines in the same scope, thus reducing the cost of coroutine management.

async function

asyncThe function can create a coroutine and get its execution result. It must be called in the scope of the coroutine. It will create a new sub-coroutine and return a Deferred object, if you want to get the execution result of the async function code block, Just call the await() method of the Deferred object.

fun main(){
    runBlocking{
        val result = async {
            5 + 5
        }. await()
        print(result)
    }
}

After calling the async function, the code in the code block will start executing immediately. When the await() method is called, if the code in the code block has not been executed, the await() method will block the current coroutine until the async function.

So when using it, instead of using the await() method to get the result immediately after calling the async function every time, but only when you need to use The await() method is called to obtain the execution result of the async function, so that multiple async functions become a parallel relationship, which can greatly improve efficiency.

withContext()

withContext() is a suspending function, which can be regarded as a simplified version of the async function in terms of usage.

fun main(){
    runBlocking{
        val result = withContext(Dispatchers. Default){
            5 + 5
        }
        println(result)
    }
}

After calling the withContext() function, the code in the code block will be executed immediately, and the external coroutine will be suspended at the same time. When all the code in the code block is executed, the execution result of the last line will be returned as the return value of the withContext() function, so it is basically equivalent to val result = async{ 5 + 5 }.await(). The only difference is that the withContext() function requires us to have a thread parameter, and specify a specific running thread for the coroutine through the thread parameter.

parameter name function
Dispatchers.Default will use a default low-concurrency thread strategy. When the code to be executed is a computationally intensive task, turning on too high concurrency may affect the operating efficiency of the task, so you can use This parameter
Dispatchers.IO will use a higher concurrent thread strategy, when the code to be executed is large Most of the time is blocked and waiting. For example, when executing network requests, in order to support a higher number of concurrency, you can use this parameter
Dispatchers.Main Will not open the child thread, but execute the code in the Android main thread, but this value can only be used in the Android project, pure Kotlin program using this type of thread parameter will appear mistake.

Using coroutines to simplify callbacks

suspendCoroutineThe function must be called in the coroutine scope or suspend function, it receives a Lambda expression parameter, the main function is to convert the current coroutine process hangs immediately,Then execute the code in the Lambda expression in a common thread. A Continuation parameter will be passed in the parameter list of the Lambda expression, and its resume() method or resumeWithException() allows the coroutine to resume execution.

suspend fun request(address: String): String {
return suspendCoroutine { continuation ->
HttpUtil.sendHttpRequest(address, object : HttpCallbackListener{
override fun onFinish(response: String) {
//If the request is successful, the suspended coroutine will be resumed, and the corresponding successful data will be passed to the server
 continuation. resume(response)
 }
override fun onError(e: Exception) {
//Resume the suspended coroutine if the request fails, and pass in the specific reason for the exception
continuation. resumeWithException(e)
}
})
}
}

In this way, no matter how many network requests are to be initiated later, there is no need to repeat the callback implementation.

suspend fun getBaiduResponse() {
try {
val response = request("https://www.baidu.com/")
// Process the data responded by the server
} catch (e: Exception) {
// Handle exceptions
}
}

getBaiduResponse() is a suspending function, so when it calls the request() function, the current coroutine will be suspended immediately, and then Wait for the network request to succeed or fail before the current coroutine can resume running. In this way, the response data of the asynchronous network request can be obtained even without using the callback method, and if the request fails, it will directly enter the catch statement.

Leave a Reply

Your email address will not be published. Required fields are marked *