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
async
The 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
function, so that multiple await()
method is called to obtain the execution result of the asyncasync
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
suspendCoroutine
The 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.