Public number: byte array, keen to share Android system source code parsing, Jetpack source code parsing, popular open source library source code parsing and other essential knowledge interview

Recently, I have been learning about kotlin coroutines, and the best learning materials are naturally the official learning documents. After reading them, I have the idea of translating the official documents. Before and after spent close to a month time, a total of nine articles, here also share out, hope to help readers. Limited by personal knowledge, some translation is not too smooth, also hope that readers can put forward their opinions

Coroutines official documentation: Coroutines – Guide

Coroutines-cn-guide coroutines-cn-guide

Coroutine official document Chinese translator: leavesC

[TOC]

This section discusses cancellation and timeouts of coroutines

Cancelling coroutine execution

In a long-running application, we might need fine-grained control over coroutines. For example, a user might close the page that started the coroutine and now no longer need the results of its run, at which point he should actively fetch the coroutine. The Job object returned by the launch function can be used to cancel the running coroutine

import kotlinx.coroutines.*

fun main(a) = runBlocking {
//sampleStart
    val job = launch {
        repeat(1000) { i ->
            println("job: I'm sleeping $i ...")
            delay(500L)
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancel() // cancels the job
    job.join() // waits for job's completion 
    println("main: Now I can quit.")
//sampleEnd    
}
Copy the code

The results

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

As long as main calls job.cancel, we don’t see any output from the job coroutine because it has been canceled. There is also a Job extension function cancelAndJoin, which combines calls to cancel and join.

The cancel() function is used to cancel the coroutine, and the join() function is used to block until the coroutine finishes executing. The reason the two methods are called consecutively is because cancel() returns immediately after the call rather than waiting for the coroutine to finish, so the coroutine does not have to stop immediately. To ensure that the coroutine is finished before the subsequent code is executed, you need to call join() to block the wait. You can do the same by calling the Job’s extension function cancelAndJoin()

public suspend fun Job.cancelAndJoin(a) {
    cancel()
    return join()
}
Copy the code

Ii. The Cancellation is cooperative.

Cancellation of coroutines is done cooperatively. Coroutines must cooperate to cancel. All pending functions in kotlinx.coroutines are cancelable; they check at runtime to see if the coroutine has been cancelled and throw a CancellationException when cancelled. However, you cannot fetch a coroutine if it is performing a computation task without checking whether it is in the cancelled state, as shown in the following example:

import kotlinx.coroutines.*

fun main(a) = runBlocking {
//sampleStart
    val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (i < 5) { // computation loop, just wastes CPU
            // print a message twice a second
            if (System.currentTimeMillis() >= nextPrintTime) {
                println("job: I'm sleeping ${i++} ...")
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
//sampleEnd    
}
Copy the code

Running the code shows that even after cancel, the coroutine job continues to print “I’m sleeping” until the job terminates after five iterations (the run condition no longer holds)

job: I'm sleeping 0 ...
job: I'm sleeping 1. job: I'm sleeping 2 ...
main: I'm tired of waiting!
job: I'm sleeping 3 ...
job: I'm sleeping 4. main: Now I can quit.Copy the code

Making computation code cancellable

There are two ways that code that computes types can be canceled. The first is to periodically call a suspended function to check for cancellations, and the yieid() function is a good choice. Another method is to display the check cancel operation. Let’s try the latter

Replace while (I < 5) in the previous example with while (isActive)

import kotlinx.coroutines.*

fun main(a) = runBlocking {
//sampleStart
    val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (isActive) { // cancellable computation loop
            // print a message twice a second
            if (System.currentTimeMillis() >= nextPrintTime) {
                println("job: I'm sleeping ${i++} ...")
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
//sampleEnd    
}
Copy the code

As you can see, the loop is now cancelled. IsActive is an extended property that can be used within a coroutine via a CoroutineScope object

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

Closing resources with finally

Cancelable suspend functions that throw a CancellationException when cancelled can be handled in the usual way. For example, try {… } finally {… Both the} expression and kotlin’s use function can be used to perform the collection when the elimination coroutine is fetched

import kotlinx.coroutines.*

fun main(a) = runBlocking {
//sampleStart
    val job = launch {
        try {
            repeat(1000) { i ->
                println("job: I'm sleeping $i ...")
                delay(500L)}}finally {
            println("job: I'm running finally")
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
//sampleEnd    
}
Copy the code

The join() and cancelAndJoin() functions wait for all reclamation operations to complete before continuing with the subsequent code, so the above example generates the following output:

job: I'm sleeping 0 ...
job: I'm sleeping 1. job: I'm sleeping 2 ...
main: I'm tired of waiting!
job: I'm running finally
main: Now I can quit.
Copy the code

Run non-cancellable block

Using the suspend function in the finally block in the previous example would cause a CancellationException to be thrown because the coroutine has already been cancelled (for example, calling delay(1000L) in finally first causes subsequent output statements not to execute). Usually this is not a problem because all well-performing close operations (closing files, canceling jobs, closing any type of communication channels, and so on) are generally non-blocking and do not involve any pending functions. However, in rare cases when a suspended function needs to be called inside a cancelled coroutine, the code can be wrapped around withContext(NonCancellable) {… }, as shown in the following example:

import kotlinx.coroutines.*

fun main(a) = runBlocking {
//sampleStart
    val job = launch {
        try {
            repeat(1000) { i ->
                println("job: I'm sleeping $i ...")
                delay(500L)}}finally {
            withContext(NonCancellable) {
                println("job: I'm running finally")
                delay(1000L)
                println("job: And I've just delayed for 1 sec because I'm non-cancellable")
            }
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
//sampleEnd    
}
Copy the code

At this point, even if the suspended function is called in the finally block, it will take effect and subsequent output statements will print as normal

job: I'm sleeping 0 ...
job: I'm sleeping 1. job: I'm sleeping 2 ...
main: I'm tired of waiting!
job: I'm running finally
job: And I've just delayed for 1 sec because I'm non-cancellable
main: Now I can quit.
Copy the code

6. Timeout

Most of the time, the reason we take the elimination coroutine is because its execution time has exceeded the estimated maximum. Although we can manually trace references to the corresponding Job and cancel the Job after a timeout, the official withTimeout function is provided to do this. Take a look at this example:

import kotlinx.coroutines.*

fun main(a) = runBlocking {
//sampleStart
    withTimeout(1300L) {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)}}//sampleEnd
}
Copy the code

Running result:

I'm sleeping 0 ...
I'm sleeping 1. I'm sleeping 2 ...
Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms
Copy the code

WithTimeout TimeoutCancellationException is a subclass of CancellationException. We’ve never seen stack information for exceptions like CancellationException on the console before. This is because for a canceled coroutine, a CancellationException is considered the normal cause for triggering the end of the coroutine. However, in this case, we use withTimeout function in the main function, the function will throw TimeoutCancellationException voluntarily

You can do this by using the try{… } the catch (e: TimeoutCancellationException) {… } block of code to perform some specific additional operation on the timeout operation in any case, or by using the withTimeoutOrNull function to return NULL on timeout instead of throwing an exception

import kotlinx.coroutines.*

fun main(a) = runBlocking {
//sampleStart
    val result = withTimeoutOrNull(1300L) {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)}"Done" // will get cancelled before it produces this result
    }
    println("Result is $result")
//sampleEnd
}
Copy the code

No exception information is displayed

I'm sleeping 0 ...
I'm sleeping 1. I'm sleeping 2 ...
Result is null
Copy the code