Detailed explanation of kotlin coroutine, it is impossible to not understand it

1. Introduction to coroutines

First of all, let’s raise a series of questions. After everyone understands these questions, it may be easier to learn Kotlin coroutines:

1. What is concurrency? What is parallelism?

2. What is multitasking? What is collaborative multitasking? What is preemptive multitasking?

3. What is synchronization? What is asynchronous?

4. What is non-blocking? What is blocking?

5. What is hang?

6. What is non-blocking suspend?

7. What is a coroutine?

8. What is a Kotlin coroutine?

9. What is the use of Kotlin coroutines?

1. What is concurrency? What is parallelism?

1) Concurrency means that only one instruction is executed at the same time. However, because the CPU time slice is very small, multiple instructions can be switched quickly, making it seem that we have the effect of simultaneous execution, which exists in single-core or multi-core CPU systems.

2) Parallelism means multiple instructions are executed at the same time, which exists in multi-core CPU systems.

Take an example of people eating steamed buns in life: if a person buys 3 steamed buns, he can only eat one steamed bun at the same time. This is concurrency. If 3 people each buy a steamed bun, they can eat the steamed buns at the same time. This is parallelism. The difference between concurrency and parallelism is whether things are happening at the same time

2. What is multitasking? What is collaborative multitasking? What is preemptive multitasking?

1) Multitasking means that the operating system can handle multiple tasks at the same time. For example, I can use my laptop to open Android Studio and NetEase Cloud Music, and listen to music while coding.

2) Collaborative multitasking means that one task gets CPU time. Unless it gives up using the CPU, it will completely occupy the CPU. Therefore, tasks need to cooperate. After using the CPU for a period of time, it gives up using it, and the same is true for other tasks. , to ensure the normal operation of the system. Typically appears in earlier operating systems, such as Windows 3.1

3) Preemptive multitasking means that the operating system allocates the CPU usage time of each task. After a task uses the CPU for a period of time, the operating system will deprive the current task of the CPU usage rights and put it at the end of the query queue. Then ask about the next task. Generally appears in currently used operating systems, such as Window 95 and later Windows versions.

The difference between cooperative multitasking and preemptive multitasking: In cooperative multitasking, if one task deadlocks, the system will also deadlock. In preemptive multitasking, if a task is deadlocked, the system can still run normally.

3. What is synchronization? What is asynchronous?

Synchronization and asynchronousness in the computer field are different from synchronization and asynchronousness in our daily lives, which makes it difficult for many people to understand

1) Synchronization in the computer field means that when the caller sends a calling instruction, it needs to wait for the instruction to be executed before continuing. It is a serial processing method.

2) Asynchronous in the computer field means that when the caller sends a calling instruction, there is no need to wait for the instruction to be executed and the execution continues. It is a parallel processing method.

4. What is blocking? What is non-blocking?

Blocking is very simple. It means literally. In Android, it actually blocks the running of the main thread. Then non-blocking means that the running of the main thread is not blocked.

5. What is hang?

Suspending means saving the current state and waiting for resumption of execution. In Android, suspending means not affecting the work of the main thread. A more appropriate statement can be understood as switching to a specified thread.

6. What is non-blocking suspend?

Through the explanation of the above concept, non-blocking suspension means that the main thread will not be stuck and the program will be switched to another specified thread for execution.

7. What is a coroutine?

Coroutine, whose English name is Coroutine, originates from Simula and Modula-2 languages. It is a collaborative multi-task implementation and a programming idea that is not limited to a specific language. The original intention of coroutine design is to solve concurrency problems and make collaborative multitasking more convenient.

8. What is a Kotlin coroutine?

Kotlin coroutines are simply a set of thread operation frameworks. To be more specific, they are a set of higher-level tool APIs implemented based on threads, similar to Java's thread pool. You can understand that Kotlin has created some new concepts for Help you better use these APIs, nothing more

9. What is the use of Kotlin coroutines?

1) Kotlin coroutines can write asynchronous code in a way that looks synchronous, helping you handle callback hell gracefully.

After clarifying the above issues, let’s look down

The most detailed introductory and advanced practice of kotlin coroutine for Android in history

Basic introduction to Kotlin coroutines

2. Basic use of Kotlin coroutines

Before talking about concepts, let’s talk about usage.

Scenario: Start the worker thread to perform a time-consuming task, and then process the results on the main thread.

Common processing methods:

  • Define callbacks yourself and process them
  • Use thread/thread pool, Callable
    thread Thread(FeatureTask(Callable)).start
    thread pool submit(Callable)
  • Android: Handler、 AsyncTask、 Rxjava

Use coroutines:

coroutineScope.launch(Dispatchers.Main) {
    
     // 在主线程启动一个协程
    val result = withContext(Dispatchers.Default) {
    
     // 切换到子线程执行
        doSomething()  // 耗时任务
    }
    handResult(result)  // 切回到主线程执行
}

What needs to be noted here is: Dispatchers.Main is unique to Android. If it is used in a java program, an exception will be thrown.

2.1 Three ways to create coroutines

  1. Created using the runBlocking top-level function:
runBlocking {
    ...
}
  1. Created using GlobalScope singleton object
GlobalScope.launch {
    ...
}
  1. Create a CoroutineScope object through CoroutineContext yourself
val coroutineScope = CoroutineScope(context)
coroutineScope.launch {
    ...
}
  • Method 1 is usually suitable for unit testing scenarios, but this method is not used in business development because it is thread blocking.
  • The difference between method two and using runBlocking is that the thread will not be blocked. However, this usage is not recommended in Android development because its life cycle will only be limited by the life cycle of the entire application and cannot be canceled.
  • Method three is the more recommended method of use. We can manage and control the life cycle of the coroutine through the context parameter (the context here is not the same thing as in Android, it is a more general concept, and there will be an encapsulation of the Android platform. With the use of).

2.2 Waiting for a job

Let’s look at an example first:

fun main() = runBlocking {
    
    
    launch {
    
    
        delay(100)
        println("hello")
        delay(300)
        println("world")
    }
    println("test1")
    println("test2")
}

The execution results are as follows:

test1
test2
hello
world

After we start a coroutine, we can keep a reference to it and wait explicitly for its execution to end. Note that the waiting here is non-blocking and will not suspend the current thread.

fun main() = runBlocking {
    
    
    val job = launch {
    
    
        delay(100)
        println("hello")
        delay(300)
        println("world")
    }
    println("test1")
    job.join()
    println("test2")
}

Output result:

test1
hello
world
test2

Analogous to java threads, there is also a join method. However, threads are operating system bound. On some CPUs, the join method may not take effect.

2.3 Cancellation of coroutine

In analogy with threads, Java threads do not actually provide any mechanism to safely terminate the thread.
The Thread class provides a method, the interrupt() method, for interrupting the execution of the thread. Calling the interrupt() method does not mean to immediately stop the work being done by the target thread, but only delivers a message requesting an interruption. The thread then interrupts itself at the next appropriate opportunity.

But the coroutine provides a cancel() method to cancel the job.

fun main() = runBlocking {
    
    
    val job = launch {
    
    
        repeat(1000) {
    
     i ->
            println("job: test $i ...")
            delay(500L)
        }
    }
    delay(1300L) // 延迟一段时间
    println("main: ready to cancel!")
    job.cancel() // 取消该作业
    job.join() // 等待作业执行结束
    println("main: Now cancel.")
}

Output result:

job: test 0 ...
job: test 1 ...
job: test 2 ...
main: ready to cancel!
main: Now cancel.

You can also use the function cancelAndJoin, which combines calls to cancel and join.

Question:
What happens if job.join() is called first and then job.cancel() is called?

Cancellation is collaborative
Coroutines cannot necessarily be canceled. The cancellation of coroutines is collaborative. A piece of coroutine code must cooperate before it can be canceled.
All suspending functions in kotlinx.coroutines are cancelable. They check for coroutine cancellation and throw CancellationException when cancelled.
If the coroutine is performing a calculation task and does not check for cancellation, it cannot be canceled.

fun main() = runBlocking {
    
    
    val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
    
    
        var nextPrintTime = startTime
        var i = 0
        while (i < 5) {
    
     // 一个执行计算的循环,只是为了占用 CPU
            // 每秒打印消息两次
            if (System.currentTimeMillis() >= nextPrintTime) {
    
    
                println("job: hello ${
      
      i++} ...")
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L) // 等待一段时间
    println("main: ready to cancel!")
    job.cancelAndJoin() // 取消一个作业并且等待它结束
    println("main: Now cancel.")
}

The printing result at this time:

job: hello 0 ...
job: hello 1 ...
job: hello 2 ...
main: ready to cancel!
job: hello 3 ...
job: hello 4 ...
main: Now cancel.

It can be seen that the coroutine has not been cancelled. In order to truly stop the coroutine from working, we need to periodically check whether the coroutine is in the active state.

Check job status
One way is to add code to check coroutine status in while(i<5)
The code is as follows:

while (i < 5 && isActive)

This means that our work will only be executed when the coroutine is in the active state.

Another method uses the function in the coroutine standard library ensureActive(), its implementation is as follows:

public fun Job.ensureActive(): Unit {
    
    
    if (!isActive) throw getCancellationException()
}

code show as below:

while (i < 5) { // 一个执行计算的循环,只是为了占用 CPU
    ensureActive()
    ...
}

ensureActive() will throw an exception immediately when the coroutine is not in the active state.

uses yield()
yield() in the same way as ensureActive.
The first work that yield will do is to check whether the task is completed. If the Job has been completed, a CancellationException will be thrown to end the coroutine. yield should be called first in a scheduled check.

while (i < 5) { // 一个执行计算的循环,只是为了占用 CPU
    yield()
    ...
}

2.4 Waiting for the execution result of the coroutine

For coroutines without return values, use the launch function to create them. If a return value is required, create them through the async function.
Use the async method to start a Deferred (also a job), and you can call its await() method to obtain the execution result.
looks like the following code:

val asyncDeferred = async {
    ...
}

val result = asyncDeferred.await()

deferred can also be canceled. Calling the await() method on a canceled deferred will throw an
JobCancellationException exception.

Similarly, if deferred.cancel() is called after deferred.await, nothing will happen because the task has ended.

The specific usage of async will be discussed later on asynchronous tasks.

2.5 Exception handling of coroutines

Since a CancellationException will be thrown when the coroutine is canceled, we can wrap the suspension function in a try/catch code block, so that the resource cleanup operation can be performed in the finally code block.

fun main() = runBlocking {
    
    
    val job = launch {
    
    
        try {
    
    
            delay(100)
            println("try...")
        } catch (e: Exception) {
    
    
            println("exception: ${
      
      e.message}")
        } finally {
    
    
            println("finally...")
        }
    }
    delay(50)
    println("cancel")
    job.cancel()
    print("Done")
}

result:

cancel
Doneexception: StandaloneCoroutine was cancelled
finally...

2.6 Timeout of coroutine

In practice, the vast majority of reasons for canceling a coroutine are that it may time out. When you manually trace a reference to a related Job and start it, use the withTimeout function.

fun main() = runBlocking {
    
    
    withTimeout(300) {
    
    
        println("start...")
        delay(100)
        println("progress 1...")
        delay(100)
        println("progress 2...")
        delay(100)
        println("progress 3...")
        delay(100)
        println("progress 4...")
        delay(100)
        println("progress 5...")
        println("end")
    }
}

result:

start...
progress 1...
progress 2...
Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 300 ms

withTimeout throws TimeoutCancellationException, which is a subclass of CancellationException. We haven't seen the stack trace printed on the console before. This is because CancellationException in a canceled coroutine is considered a normal reason for the end of coroutine execution. However, in this example we are using withTimeout correctly in the main function. If necessary, we need to actively catch exceptions and handle them.

Of course, there is another way: using withTimeoutOrNull.

withTimeout can be returned by a value. Executing the withTimeout function will block and wait for the result to be returned or timeout to throw an exception. withTimeoutOrNull is used the same as withTimeout, except that it returns null after timeout.

Summarize

In fact, it is very simple to master it easily. There are only two key points:

  1. Find a good set of video materials and follow the knowledge framework compiled by the experts to study.
  2. Practice more. (The advantage of video is that it is highly interactive and easy to concentrate)

You don’t need to be a genius, nor do you need to have strong talents. As long as you do these two things, the probability of success in the short term is very high.

For many junior and intermediate Android engineers, if they want to improve their skills, they often have to explore and grow on their own. The unsystematic learning effect is inefficient, long and unhelpful. The screenshots of some of the information below were compiled by me over several months, and they are full of sincerity: it is especially suitable for Android programmers with 3-5 years of development experience to learn from.

  • Download the direct link to get it yourself
  • The above advanced BATJ factory learning materials can be shared with everyone for free. Friends who need the full version can scan the QR code below.

Guess you like

Origin blog.csdn.net/Code1994/article/details/129448142