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 code
job.cancel()
andjob.join()
- As the name implies
job.cancel()
It must be a cancel operation - while
job.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 sentence
main:Now I can quit!
Print the - Of course the authorities do
job.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 the
job.cancelAndJoin()
Code when the corresponding passlaunch(Dispatchers.Default)
Inside the coroutine that was createdisActive
Attribute 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!