This is the first day of my participation in the August Text Challenge.More challenges in August

The first coroutine

In essence, coroutines are lightweight threads. They start with some coroutine builder. Here we start a new coroutine in [GlobalScope], which means that the life of the new coroutine is limited only by the life of the entire application

var globalScopeFun = GlobalScope.launch( context = Dispatchers.IO) {
    delay(3000)
    Log.i("coroutines"."GlobalScope: hello world")}class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Log.i("coroutines"."GlobalScope:start")

        globalScopeFun// Coroutine code

        Log.i("coroutines"."GlobalScope:end")}}Copy the code
GlobalScope:start
GlobalScope:end
GlobalScope:hello world
Copy the code

In the example above, a GlobalScope coroutine is launched and the log entry is delayed by three seconds. The result is that globalScope.launch {… } is replaced by thread {…… }, and delay(……) Replace with Thread. Sleep (…) To the same end. But there are differences between coroutines and threads. Coroutines are much better managed, can greatly improve concurrency efficiency, reduce code callbacks and improve readability.

  1. From the above code we can derive four concepts
  1. Suspend Fun: Delay is a suspend function “public suspend Fun Delay (timeMillis: Long)” that delays the coroutine without blocking the thread for a given amount of time and resumes the coroutine after a specified time.
  2. CoroutineScope: Defines the scope of the new coroutine. GlobalScope is the CoroutineScope implementation class that manages the life cycle of coroutines, which need to be started through the CoroutineScope
  3. IO is the abstract CoroutineContext that defines the maximum number of threads that Dispatchers can use. IO) coroutine scheduler
  4. CoroutineBuilder: CoroutineContext is launched with the coroutine constructor launch async declaration, which is an extension of CoroutineScope

Through the above four concepts, we analyze their principles and application scenarios one by one

Suspend fun suspends a function

  1. The error message

If you first replace globalScope. launch with thread, the compiler will raise the following Error: Error: Kotlin: Suspend functions are only allowed to be called from a coroutine or another suspend function

This is because delay is a special suspend function that does not block threads, but suspends coroutines and can only be used in coroutines.

public suspend fun delay(timeMillis: Long)
Copy the code

Delays coroutine for a given time without blocking a thread. Delay Delays coroutine for a given time without blocking a thread Delay a coroutine and not block a thread for a specified time. And resume the coroutine after a specified time, Delay functions are similar to Thread.sleep() in Java, and delay does not block threads because the life cycle of threads is controlled by the system, and delay of threads can be given to developers to control, and there is a saying in the source code: “This kickbacks kickbacks If the Job of the current coroutine is cancelled or completed while the suspended function is waiting, the function resumes immediately, so delay does not block.

The execution of the coroutine

fun main(a) = runBlocking {
    val job = launch {
        repeat(1000) { i ->
            println("job: I'm sleeping $i ...")
            delay(500L)
        }
    }
    delay(1300L) // Delay some time
    println("main: I'm tired of waiting!")
    job.cancel() // Cancel the job
    job.join() // Wait for the job to finish
    println("main: Now I can quit.")}Copy the code

The output of the program is as follows:

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

Once main calls job.cancel, we don’t see any output in the other coroutines because it’s canceled.

Coroutine scope

Let’s put together what we know about context, subcoroutines, and jobs. Suppose our application has an object with a life cycle, but this object is not a coroutine. For example, we wrote an Android application and started a set of coroutines in the Android Activity context to pull and update data using asynchronous operations, perform animations, and so on. All of these coroutines must be cancelled when the activity is destroyed to avoid a memory leak. Of course, we could also manipulate the context and job manually to combine the activity’s life cycle with its coroutines, but Kotlinx.coroutines provides a wrapper: the [CoroutineScope] abstraction. You should already be familiar with coroutine scope, since all coroutine builders are declared as extensions on top of it.

We manage the coroutine lifecycle by creating an instance of [CoroutineScope] and associate it with the activity lifecycle. CoroutineScope can be created by [CoroutineScope()] or by the [MainScope()] factory function. The former creates a generic scope, while the latter creates a scope for UI applications that use [dispatchers.main] as the default scheduler

class Activity {
    private val mainScope = MainScope()

    fun destroy() {
        mainScope.cancel()
    }
    // Continue running......
Copy the code

Now we can use the defined scope to start the coroutine within the scope of the Activity. For this example, we started ten coroutines that delayed for different times:

// In the Activity class
    fun doSomething() {
        // In the example, 10 coroutines are started, each working for a different duration
        repeat(10) { i ->
            mainScope.launch {
                delay((i + 1) * 200L) // Delay 200ms, 400ms, 600ms, etc
                println("Coroutine $i is done")}}}}// End of the Activity class
Copy the code

In the main function we create the activity, call the test function doSomething, and destroy the activity after 500 milliseconds. This cancels all coroutines started from doSomething. We can observe this because after the destruction, the activity no longer prints messages, even if we wait a little longer.

import kotlinx.coroutines.*

class Activity {
    private val mainScope = CoroutineScope(Dispatchers.Default) // use Default for test purposes
    
    fun destroy(a) {
        mainScope.cancel()
    }

    fun doSomething(a) {
        // In the example, 10 coroutines are started, each working for a different duration
        repeat(10) { i ->
            mainScope.launch {
                delay((i + 1) * 200L) // Delay 200ms, 400ms, 600ms, etc
                println("Coroutine $i is done")}}}}// End of the Activity class

fun main(a) = runBlocking<Unit> {
    val activity = Activity()
    activity.doSomething() // Run the test function
    println("Launched coroutines")
    delay(500L) // Delay half a second
    println("Destroying activity!")
    activity.destroy() // Cancel all coroutines
    delay(1000) // To visually confirm that they are not working
}
Copy the code

The output of this example looks like this:

Launched coroutines
Coroutine 0 is done
Coroutine 1 is done
Destroying activity!
Copy the code

As you can see, only the first two coroutines print messages, while the other coroutine calls job.cancel() a single time in activity.destroy ().

Using async

  1. Use async concurrency

Conceptually async is similar to launch. It starts a single coroutine, which is a lightweight thread that works concurrently with all other coroutines. The difference is that launch returns a Job with no result value, while Async returns a Deferred — a lightweight, non-blocking future that represents a promise that will provide a result at a later date. You can use.await() to get its final result on a Deferred value, but Deferred is also a Job, so you can cancel it if needed.

import kotlinx.coroutines.*
import kotlin.system.*

fun main(a) = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = async { doSomethingUsefulOne() }
        val two = async { doSomethingUsefulTwo() }
        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")}suspend fun doSomethingUsefulOne(a): Int {
    delay(1000L) // Suppose we do something useful here
    return 13
}

suspend fun doSomethingUsefulTwo(a): Int {
    delay(1000L) // Suppose we do something useful here
    return 29
}
Copy the code

Its printout looks like this:

The answer is 42
Completed in 1017 ms
Copy the code

This is twice as fast, because two coroutines are executing concurrently. Note that concurrency with coroutines is always explicit.

  1. Lazy start async

Optionally, [async] can be made LAZY by setting the start parameter to [CoroutineStart.LAZY]. In this mode, the coroutine is started only when the result is obtained by [await], or when the [start] function of the Job is called. Run the following example

import kotlinx.coroutines.*
import kotlin.system.*

fun main(a) = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
        val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
        // Perform some calculations
        one.start() // start the first
        two.start() // Start the second
        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")}suspend fun doSomethingUsefulOne(a): Int {
    delay(1000L) // Suppose we do something useful here
    return 13
}

suspend fun doSomethingUsefulTwo(a): Int {
    delay(1000L) // Suppose we do something useful here
    return 29
}
Copy the code

Its printout looks like this:

The answer is 42
Completed in 1017 ms
Copy the code

Thus, in the previous example the two coroutines defined here were not executed, but control rests with the programmer calling [start] exactly at the beginning of execution. We call one first, then two, and wait for the coroutine to complete.

Note that if we just call [await] in println without calling [start] in a separate coroutine, this will result in sequential behavior until [await] starts the coroutine execution and waits until it finishes, which is not the expected use case of inertia. The async(start = coroutinestart.lazy) use case is used to replace the LAZY function in the standard library when computing a value involving a suspended function.

infinallyRelease resources in

We usually use the following method to handle a CancellationException that can be cancelled when it is cancelled. For example, try {… } the finally {… } expressions and Kotlin’s use function generally perform their finalizing actions when the coroutine is canceled:

import kotlinx.coroutines.*

fun main(a) = runBlocking {
    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 some time
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // Cancel the job and wait for it to finish
    println("main: Now I can quit.")}Copy the code

Join and cancelAndJoin wait for all terminations to complete, so running the example yields 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