Ali P7 boss teaches you Kotlin coroutine (2): coroutine start article

Solution to Kotlin Coroutine (2) - Coroutine Startup

Now that you know what coroutines are all about, you might want to try them yourself. This article will give you a detailed introduction to the differences between the several startup modes of coroutines. Of course, I don’t plan to go deep into the principle of source code analysis now. You only need to remember these rules to use coroutines well.

1. Recall when you first learned Thread

I believe that the vast majority of developers who come into contact with Kotlin now have a Java foundation. When we first started learning Thread, we must have done this:

val thread = object : Thread(){
    override fun run() {
        super.run()
        //do what you want to do.
    }
}
thread.start()

Someone must have forgotten to call it start, and I am particularly wondering why the thread I opened does not start. To be honest, the design startof is actually very strange, but I understand the designers, after all, it was still stopavailable back then, but they soon found that the design stopwas a mistake, and it was abandoned in JDK 1.1 because it was not safe, which is called The above is the most short-lived API.

Since stopit is a mistake, startisn't it also a mistake that always makes beginners lose it?

Ha, I'm digressing a bit. We mainly talk about Kotlin today. The designers of Kotlin had a great idea, and they provided a convenient method for threads:

val myThread = thread {
    //do what you want
}

This threadmethod has a parameter startthat defaults to true, in other words, the thread created in this way is started by default, unless you really don't want it to go to work right away:

val myThread = thread(start = false) {
    //do what you want
}
//later on ...
myThread.start()

It looks much more natural this way. The interface design should allow the default value to meet 80% of the requirements.

2. Let's look at the start of the coroutine

Having said so many threads, the reason is, after all, everyone is most familiar with it. The API design of the coroutine is actually in the same line. Let's look at the simplest way to start the coroutine:

GlobalScope.launch {
    //do what you want
}

So how does this code execute? As we said, three things are needed to start a coroutine, namely the context , the startup mode , and the coroutine body . The coroutine body is like the code Thread.runin it , needless to say.

This article will introduce the startup . In Kotlin coroutines, the launch mode is an enumeration:

public enum class CoroutineStart {
    DEFAULT,
    LAZY,
    @ExperimentalCoroutinesApi
    ATOMIC,
    @ExperimentalCoroutinesApi
    UNDISPATCHED;
}

|
mode

|

Function

|
| — | — |
|

DEFAULT

|

Execute the coroutine body immediately

|
|

ATOMIC

|

Executes the body of the coroutine immediately, but cannot be canceled before it starts running

|
|

UNDISPATCHED

|

Immediately execute the body of the coroutine in the current thread until the first suspend call

|
|

LAZY

|

run only when needed

|

2.1 DEFAULT

Among the four startup modes, the most commonly used ones are actually DEFAULTand LAZY.

DEFAULTIt is a hungry Chinese style startup. launchAfter calling, it will enter the waiting state immediately. Once the scheduler is OK, it can start to execute. Let's look at a simple example:

suspend fun main() {
    log(1)
    val job = GlobalScope.launch {
        log(2)
    }
    log(3)
    job.join()
    log(4)
}

Note: The main function supports suspend starting from Kotlin 1.3. In addition, the omission of parameters in the main function is also a feature of Kotlin 1.3. The following examples are directly run in the suspend main function without special instructions.

This program uses the default startup mode. Since we did not specify a scheduler, the scheduler is also the default. On the JVM, the implementation of the default scheduler is similar to that of other languages. It has some threads in the background to handle asynchronous task, so the running result of the above program may be:

19:51:08:160 [main] 1
19:51:08:603 [main] 3
19:51:08:606 [DefaultDispatcher-worker-1] 2
19:51:08:624 [main] 4

It could also be:

20:19:06:367 [main] 1
20:19:06:541 [DefaultDispatcher-worker-1] 2
20:19:06:550 [main] 3
20:19:06:551 [main] 4

It depends on the CPU's scheduling order for the current thread and the background thread, but don't worry, you will soon find that the output order of 2 and 3 in this example is not that important.

You may have already guessed the implementation of the default scheduler on the JVM. Yes, a thread pool is opened, but a few threads are enough to schedule thousands of coroutines, and each coroutine has its own call stack. This is fundamentally different from purely opening a thread pool to perform asynchronous tasks. Of course, we say that Kotlin is a cross-platform language, so the above code can also run in a JavaScript environment, such as Nodejs. In Nodejs, the default scheduler of Kotlin coroutines does not implement thread switching, and the output results will be slightly different, which seems to be more in line with the execution logic of JavaScript. We will discuss more about the scheduler later.

2.2 LAZY

LAZYIt is a lazy startup, launchand there will be no scheduling behavior afterwards, and the coroutine body will naturally not enter the execution state until we need it to execute. This is actually a bit confusing, what do we mean when we need it to execute? That is, when its operation result is needed, launchan Jobinstance . For this case, we can:

  • Call Job.start, actively trigger the scheduled execution of the coroutine
  • call Job.join, which implicitly triggers the scheduled execution of the coroutine

So this so-called "need" is actually a very interesting wording, and you will see later that we can also use awaitto express Deferredthe need for . This behavior Thread.joindiffers , where calling joinhas no effect if it is not started.

log(1)
val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
    log(2)
}
log(3)
job.start()
log(4)

Based on this, for the example above, the output might be:

14:56:28:374 [main] 1
14:56:28:493 [main] 3
14:56:28:511 [main] 4
14:56:28:516 [DefaultDispatcher-worker-1] 2

Of course, if you are lucky enough, there may also be a situation where 2 to 4 is ahead. And for join,

...
log(3)
job.join()
log(4)

Because it is necessary to wait for the execution of the coroutine to complete, the output result must be:

14:47:45:963 [main] 1
14:47:46:054 [main] 3
14:47:46:069 [DefaultDispatcher-worker-1] 2
14:47:46:090 [main] 4

2.3 ATOMIC

ATOMICIt only makes sense when cancel is involved. Cancel itself is also a topic worthy of detailed discussion. Here we simply think that the coroutine will be canceled after cancel, that is, it will no longer be executed. Then the timing of calling cancel is different, and the results are also different, such as before the coroutine is scheduled, the scheduling is started but not yet executed, the execution has already started, the execution is completed, and so on.

In order to understand the difference between it DEFAULTand , let's look at an example:

log(1)
val job = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
    log(2)
}
job.cancel()
log(3)

We cancel immediately after creating the coroutine, but because of ATOMICthe mode , the coroutine will be scheduled, so 1, 2, and 3 will be output, but the order of 2 and 3 is hard to say.

20:42:42:783 [main] 1
20:42:42:879 [main] 3
20:42:42:879 [DefaultDispatcher-worker-1] 2

Correspondingly, if it is DEFAULTthe mode , if cancel has been called when the coroutine is scheduled for the first time, then the coroutine will be canceled directly without any call. Of course, it is also possible that the coroutine has not been canceled at the beginning, then it You can start normally. So if the previous example is changed DEFAULTto mode , then 2 may or may not be output.

It should be noted that the cancel call will definitely set the status of the job to canceling, but the coroutine in ATOMICthe mode ignores this status when it starts. To demonstrate this, we can make the example slightly more complex:

log(1)
val job = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
    log(2)
    delay(1000)
    log(3)
}
job.cancel()
log(4)
job.join()

We add one between 2 and 3 delay, delaywhich will cause the execution of the coroutine body to be suspended, and the latter part will be scheduled again after 1000ms, so 3 will be output 1000ms after 2 is executed. For ATOMICthe mode , we have already discussed that it must be started. In fact, its execution will not stop until it encounters the first suspend point, delaybut a suspend function. At this time, our coroutine ushers in its first suspend function. A suspension point happens delayto support cancel, so the following 3 will not be printed.

When we use threads, we will face similar problems if we want to stop the execution of tasks in the thread, but unfortunately, the stop interface in the thread that looks similar to cancel has been abandoned because of some security issues. But as we continue to explore in depth, you will find that the cancel of the coroutine is more like the interrupt of the thread in a sense.

2.4 UNDISPATCHED

With the previous foundation, UNDISPATCHEDit is easy to understand. In this mode, the coroutine will directly start executing under the current thread until the first suspension point, which sounds a bit like the previous one, but the difference is that the ATOMICcoroutine body is executed UNDISPATCHEDwithout any scheduler. Of course, the execution after encountering the suspension point depends on the logic of the suspension point itself and the scheduler in the context.

log(1)
val job = GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) {
    log(2)
    delay(100)
    log(3)
}
log(4)
job.join()
log(5)

We still use such an example to understand UNDISPATCHEDthe mode . According to our previous discussion, the coroutine will be executed immediately in the current thread after it is started, so 1 and 2 will be executed continuously in the same thread, delaywhich is the suspension point, so 3 will wait 100ms later Scheduling again, at this time 4 is executed, and joinit is required to wait for the coroutine to be executed, so wait for 3 to output before executing 5. The following are the results of the operation:

22:00:31:693 [main] 1
22:00:31:782 [main @coroutine#1] 2
22:00:31:800 [main] 4
22:00:31:914 [DefaultDispatcher-worker-1 @coroutine#1] 3
22:00:31:916 [DefaultDispatcher-worker-1 @coroutine#1] 5

The thread name is in the square brackets. We found that when the coroutine is executed, the thread name will be modified to make itself appear to have a sense of existence. The running results seem to have another detail that may be confusing. The joinsubsequent 5 threads are the same as 3. Why? We mentioned earlier that our examples all run in the suspend main function, so the suspend main function will help us directly start a coroutine, and the coroutines in our example are all its sub-coroutines, so the scheduling of 5 here depends on The scheduling rules of the outermost coroutine. We will talk about the scheduling of coroutines later.

3. Summary

This article uses some examples to gradually unveil the veil of coroutines.

Scan the QR code to get more Kotlin-related learning materials for free!

" Advanced Kotlin Enhanced Combat "

Chapter 1 Kotlin Getting Started Tutorial

​ ● Kotlin overview

​ ● Kotlin vs. Java

​ ● Skillful use of Android Studio

​ ● Understand the basic types of Kotlin

​ ● Walk into Kotlin's array

​ ● Walk into Kotlin collections

​ ● Complete code

​ ● Basic grammar

img

Chapter 2 Kotlin Practical Pit Avoidance Guide

​ ● Method input parameters are constants and cannot be modified

​ ● No Companion, INSTANCE?

​ ● Java overloading, how to make a clever transition in Kotlin?

​ ● Null gesture in Kotlin

​ ● Kotlin overrides the method in the Java parent class

​ ● Kotlin becomes "ruthless", even TODO is not spared!

​ ● Pitfalls in is and as`

​ ● Understanding of Property in Kotlin

​ ● also keyword

​ ● takeIf keyword

​ ● How to write singleton mode

img

Chapter 3 Project Combat "Kotlin Jetpack Combat"

​ ● Start with a demo that worships a great god

​ ● What is the experience of writing Gradle scripts in Kotlin?

​ ● The Triple Realm of Kotlin Programming

​ ● Kotlin higher order functions

​ ● Kotlin Generics

​ ● Kotlin Extensions

​ ● Kotlin delegates

​ ● Coroutine "unknown" debugging skills

​ ● Graphical coroutine: suspend

img

" The most detailed Android version of kotlin coroutine entry advanced combat in history "

Guess you like

Origin blog.csdn.net/Android_XG/article/details/129858950