This is the 17th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

preface

In the previous article, a preliminary understanding of Kotlin coroutines was given. In this article, we’ll look at Kotlin’s cancel timeout combination hang function!

No more words, just get started!

1. Suspend functions

Take a look at this example:

fun main(a) = runBlocking<Unit> {// this : CoroutineScope
    launch {
        delay(1000L)
        println("Kotlin!")
    }
    println("Hello,")}Copy the code

This is an example from the previous post, where we saw that in the Launch closure, we filled in the logic we implemented, but in real life, most of the logic was put in the method and then called the corresponding method.

So let’s try it out for real use:

fun main(a) = runBlocking<Unit> {
    launch { doKotlin() }
    println("Hello,")}fun doKotlin(a){
    delay(1000L) // This code is in error
    println("Kotlin!")}Copy the code

We found that it was wrong! Isn’t that supported? There is a hint on it, click on it and see!

fun main(a) = runBlocking<Unit> {
    launch { doKotlin() }
    println("Hello,")}// This is your first suspend function
suspend fun doKotlin(a){
    delay(1000L)
    println("Kotlin!")}Copy the code

Now the code does not report an error, run to see the effect:

Hello,
Kotlin!
Copy the code

OK, we see that the method is preceded by an additional suspend keyword so that the method can be called internally within the coroutine!

So what happens if we say that non-coroutines call this method?

suspend fun doKotlin(a){
    delay(1000L)
    println("Kotlin!")}fun main(a){
    doKotlin() // This is not true.
}
Copy the code

This is still an error! The compiler prompts us to add the suspend keyword to main as well!

By now, it should be clear that when you encounter a method with the keyword suspend, the first thing to think about is that it can only be called inside a coroutine!

2. Verify the lightness of coroutines

As mentioned in the previous article, a coroutine is actually a lightweight thread that can be suspended and resumed later.

So here to verify the coroutine lightweight in the concrete!

fun main(a) = runBlocking<Unit> {
    repeat(100000){
        launch {
            delay(5000)
            print(".")}}}Copy the code

The repeat keyword is used here to see what it means:

public inline fun repeat(times: Int, action: (Int) - >Unit) {
    contract { callsInPlace(action) }

    for (index in 0 until times) {
        action(index)
    }
}
Copy the code

Through this code, it is clear that there is a loop inside, and the contents of the loop are the contents of the corresponding closure!

That is: here a hundred thousand coroutines are initiated by repeat(100000), so that each coroutine prints the decimal point uniformly after 5 seconds.

Let’s see how it works:

As is shown in

When the run waits for 5 seconds, the decimal points are unified in an instant.

If you use Java threads to implement, it is very likely to directly generate out of memory errors!

So, coroutines are lightweight threads!

Moreover, a global coroutine is like a daemon thread: active coroutines started in GlobalScope do not keep the process alive.

3. Global coroutines are like daemon threads

fun main(a) = runBlocking<Unit> {
    GlobalScope.launch {
        repeat(1000){ i ->
            println("I'm sleeping $i")
            delay(10L)
        }
    }
    delay(130L)
    println("over ")}Copy the code

Now we see that the corresponding active coroutine has been created via GlobalScope.launch, which will print the corresponding 1000 cycles every 10L. Only 130L is suspended outside the active coroutine, and finally print!

Let’s see how it works:

I'm sleeping 0
I'm sleeping 1
I'm sleeping 2
I'm sleeping 3
I'm sleeping 4
I'm sleeping 5
I'm sleeping 6
I'm sleeping 7
I'm sleeping 8
I'm sleeping 9
I'm sleeping 10
I'm sleeping 11
over 
Copy the code

We see that the active coroutine started in GlobalScope does not keep the process alive when the main thread ends.

What if you removed the GlobalScope keyword?

fun main(a) = runBlocking<Unit> {
    launch {
        repeat(1000){ i ->
            println("I'm sleeping $i")
            delay(10L)
        }
    }
    delay(130L)
    println("over ")}Copy the code

Running effect

As is shown in

Here we see that when the main thread completes, non-global coroutines continue to execute, and the main thread does not complete until the coroutine completes.

So what if I want to force the non-global coroutine to end when the main thread ends?

4. The execution of remove coroutine

fun main(a) = runBlocking<Unit> {
    val job = launch {
        repeat(1000){ i ->
            println("job:I'm sleeping $i")
            delay(10L)
        }
    }
    delay(130L)
    println("main: I'm tired of waiting!")
    job.cancel()
    job.join()
    println("main:Now I can quit!")}Copy the code

Running effect

job:I'm sleeping 0
job:I'm sleeping 1
job:I'm sleeping 2
job:I'm sleeping 3
job:I'm sleeping 4
job:I'm sleeping 5
job:I'm sleeping 6
job:I'm sleeping 7
job:I'm sleeping 8
job:I'm sleeping 9
job:I'm sleeping 10
job:I'm sleeping 11
main: I'm tired of waiting!
main:Now I can quit!
Copy the code

Here we see:

  • In the codejob.cancel()andjob.join()
  • As the name impliesjob.cancel()It must be a cancel operation
  • whilejob.join()As mentioned in the previous article, this is waiting for the coroutine to complete
  • That is, it cancels the coroutine and then waits for all coroutine operations (including cancellations) to complete
  • You can execute the last sentencemain:Now I can quit!Print the
  • Of course the authorities dojob.cancelAndJoin()Method, combine the two into a method
fun main(a) = runBlocking<Unit> {
    val job = launch {
        repeat(1000){ i ->
            println("job:I'm sleeping $i")
            delay(10L)
        }
    }
    delay(130L)
    println("main: I'm tired of waiting!")
// job.cancel()
// job.join()
    job.cancelAndJoin()
    println("main:Now I can quit!")}Copy the code

Run the same as above!

Can all coroutines be cancelled as quickly as they were just executed?

Of course not!

Cancellation of coroutines is collaborative

fun main(a) = runBlocking<Unit> {
    val startTime = System.currentTimeMillis()
    val job = launch {
        var nextPrintTime = startTime
        var i = 0
        // There is no suspend function here
        while(i < 50) {// A loop that performs calculations, just to occupy the CPU
            if(System.currentTimeMillis() >= nextPrintTime){
                println("job:I'm sleeping ${i++} ...")
                nextPrintTime += 10L
                //delay(1L) // analysis 1
            }
        }
    }
    delay(130L)
    println("main:I'm tired of waiting!")
    job.cancelAndJoin()
    println("main:Now I can quit.")}Copy the code

Running effect

job:I'm sleeping 0 ... . A little... 'I just the job.m sleeping 49 ...
main:I'm tired of waiting!
main:Now I can quit.
Copy the code

The delay suspension function is not used here, and it is found that the main thread can only be closed until the coroutine completes execution.

Uncomment analysis 1 to see what happens:

job:I'm sleeping 0 ... . 'I just the job.m sleeping 15 ...
main:I'm tired of waiting!
main:Now I can quit.
Copy the code

Here you can see that when you uncomment analysis 1, even if it is suspended for 1 millisecond, cancellation will force cancellation as in the previous example;

So, if the coroutine is performing a computation and does not check for cancellation when delay is not used, it cannot be cancelled.

As mentioned here, no check cancels. So how does check cancel work?

6. Inspection is cancelled

fun main(a) = runBlocking<Unit> {
    val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        //isActive is an extended property that can be used in CoroutineScope
        while(i < 50 && isActive){
            if(System.currentTimeMillis() >= nextPrintTime){
                println("job:I'm sleeping ${i++} ...")
                nextPrintTime += 10L
            }
        }
    }
    delay(130L)
    println("main:I'm tired of waiting!")
    job.cancelAndJoin()
    println("main:Now I can quit.")}Copy the code

You can see that there is dispatchers. Default in launch(), followed by isActive judgment in while.

Let’s see how it works

Running effect

job:I'm sleeping 0 ... . A little... 'I just the job.m sleeping 15 ...
main:I'm tired of waiting! Job :I' job:I'm sleeping 16 ...
main:Now I can quit.
Copy the code

From this run, you can see:

  • When performing thejob.cancelAndJoin()Code when the corresponding passlaunch(Dispatchers.Default) Inside the coroutine that was createdisActiveAttribute will befalse.
  • This way, when the main thread ends, the non-main coroutine corresponding to the non-suspended function can also finish running!

conclusion

Well, that’s almost the end of this piece! You are familiar with coroutine suspend and cancel operations. In the next article, we’ll take a closer look at coroutines!