This is the second day of my participation in the August More text Challenge. For details, see: August More Text Challenge

📑 Soon to learn

Coroutines

✨ Pre-school review

thread

A thread is a path through which code is executed. Each line of code corresponds to one or more related instructions, which are executed in sequence on the same thread. In the execution of threads, the scheduler will allocate a time slice for each thread, and the thread will schedule according to the time slice algorithm. The thread concerned either completes within that time slice or hangs until it gets another time slice. In our actual project, there are other threads besides the main thread. The processor switches back and forth between different sets of instructions, presenting a state of multitasking. Allowing multiple codes to be executed out of order, or nearly concurrently, to make more efficient use of resources, operating systems can manage multitasking according to the characteristics of the system, programming language, and concurrent units. For example, the application interface responds to user operations, and the application performs complex tasks (such as network requests and image downloads) in the background, which is a concept of “concurrency”.

Many problems can arise from the simple use of threads for multitasking and concurrency

Since the main thread is responsible for using the application interface, this thread has high performance so that the application can run smoothly, and any long-running task will block the thread until it completes, causing the application to become unresponsive.

Currently, phones try to update the interface 60 to 120 times per second. At a refresh rate of 60, Android takes 16ms or less to refresh the interface each time. When the main thread doesn’t have enough time to update, Android stops trying to complete a single update cycle, losing frames in an attempt to catch up. Frame loss and frame fluctuation are normal phenomena. However, too many frames are lost and the application becomes unresponsive.

Therefore, we need to deal with the scheduling problem of such concurrent operations so that the application does not block during such operations and cause the application to crash.

In multithreading, we also encounter competition, where multiple threads access the same value in memory at the same time, resulting in dirty data, which can lead to application crash.

To this end, the kotlin functionality of coroutines has been officially introduced to write clean, non-blocking concurrent code.

Coroutines role

  • Processing time-consuming tasks Such tasks often block the main thread
  • Secure the main thread Ensures that any suspend functions are safely called from the main thread

AsyncTask and coroutines

However, we learned about asynchrony earlier in the development process, and you can use asynchrony to process time-consuming operations and then switch back to the main thread to perform UI responses. How is this different from our coroutine?

AsyncTask

AsyncTask is an asynchronous operation. Let’s look at some asynchronous code

val submitButton = findViewById<Button>(R.id.bt_submit_main).also {
            it.setOnClickListener {
                object : AsyncTask<Void.Void.Int> () {override fun doInBackground(vararg params: Void?).: Int {
                        // Time-consuming operation
                        return 5;
                    }
                    // The return value of the callback method after the operation is completed
                    override fun onPostExecute(result: Int?). {
                        if(result ! =null) {
                            nameTv.setText(result)
                        }
                    }
                }.execute()
}
Copy the code

We can see that in the process of implementing Async, we do time-consuming operations in the doInBackground, and then we override its onPostExecute callback method, in the callback method, we get the results of the previous time-consuming operations, related processing. Let’s compare coroutines to concurrent processing operations

                GlobalScope.launch(Dispatchers.Main) {
                    withContext(Dispatchers.IO){
                        // Time-consuming operation
                        delay(1200)}// Process the result
                }
Copy the code

As we can see, by handling coroutines, our program presents a sense of synchronous code, which is a kind of asynchronous code synchronization of coroutines, that’s one aspect.

On the other hand, in our processing with async, we can see that the processing of the result is handled by callback, and callback can cause callback hell, so we can try to use coroutines to handle this kind of concurrent operation.

And coroutines can achieve lightweight concurrency

Achieve higher resource utilization

Coroutine suspend and resume

The general function base operations include invoke and return. The addition of coroutines increases suspend and Resume

  • Suspend Suspend or suspend is used to suspend execution of the current coroutine and save all local variables
  • Resume is used to resume a suspended coroutine from where it was suspended

The first coroutine

  
fun main(a) {
    GlobalScope.launch(Dispatchers.Main) { // Start a new coroutine in the background and continue
        delay(1000L) // A non-blocking wait of 1 second (default time unit is milliseconds)
        println("World!") // Print output after delay
    }
    println("Hello,") // The main thread continues while the coroutine is already waiting
    Thread.sleep(2000L) Block the main thread for 2 seconds to keep the JVM alive
}
Copy the code

Code hangs and blocking hangs play a role

In the above code, we can see hang and block

  • Suspend non-blocking When we execute the suspend function within a click event, we see that the onclick () event quickly breaks out of the loop and continues after the blocking operation has completed
  • We execute the blocking function within the click event, and we’ll see that the onclick() function doesn’t complete until the time-consuming operation has completed. It’s just a blocking process. When the blocking function is executed several times in the click event, when the time reaches a certain point, the application will not respond, resulting in the application crash.

Above we have seen a partial implementation of the coroutine and some pending operations

Our coroutine implementation consists of two parts

  • The coroutine API of the standard library at the infrastructure layer provides basic conceptual and semantic support for coroutines
  • Upload framework support for business framework coroutines

What we have just used is a business framework layer implementation, but the use of coroutines in the infrastructure layer is more complex, so let’s look at the specific code implementation

                 // This simply creates a coroutine
                val continuation =suspend {
                    / / coroutines
                    5 
                }.createCoroutine(object : Continuation<Int> {//CoroutineContext CoroutineContext
                    override val context: CoroutineContext
                        get() = EmptyCoroutineContext;
                    // Return the result of executing the coroutine body. This is a callback that actually generates the code that has the callback
                    override fun resumeWith(result: Result<Int>) {
                        println(result)
                    }
                
                })
                // The instance object holds the starting point of the coroutine
                continuation.resume(Unit)
Copy the code

In the infrastructure layer we call the Kotlin package

In the business framework layer we call the Kotlinx package

We can see how asynchronous code synchronization in the business framework layer is intuitive, simple, and without callbacks. In the infrastructure layer, when we need to use coroutines, creating coroutines using the Suspend write body, creating coroutines using the createCoroutine, and building Continuation inner classes, we find the use of callbacks involved. And in that case, we see that a Continuation holds the suspension start, and in a Continuation, which is actually the CoroutineContext CoroutineContext, holds the suspension start information and we can go into the implementation code for a Continuation

@SinceKotlin("1.3")
public interface Continuation<in T> {
    /** * The context of the coroutine that corresponds to this continuation. */
    public val context: CoroutineContext

    /** * Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the * return value  of the last suspension point. */
    public fun resumeWith(result: Result<T>)
}
Copy the code

We can see that this is an interface, and that there is only a CoroutineContext instance variable. Therefore, the coroutine context holds the mount point information

Coroutine scheduler

And all coroutines need to be implemented on the coroutine scheduler. In Kotlin, these kinds of coroutine schedulers are mainly used

  • Dispatchers.Main

    • The main thread on Android handles UI interactions and some lightweight tasks

      • Call the Suspend function
      • Calling UI functions
      • Update LiveData
  • Dispatchers.Default

    • The non-main thread is optimized for CPU-intensive tasks

      • Sort an array
      • JSON data parsing
      • Treatment difference judgment
  • Dispatchers.IO

    • The secondary thread is optimized for disk and system IO

      • The database

      • network

      • File to read and write

Coroutine task leakage

During our implementation, the following scenarios will occur: Open the activity to open the coroutine to make a network request to go back to the previous activity. At this time, if the activity has been destroyed, but the network request is still there, the task leakage will occur

At this point, the coroutine task will be lost and untraceable, resulting in wasted resources such as memory, CPU, disk, or even sending a useless network request. This situation is called task leakage

Kotlin introduced a structured concurrency mechanism to avoid task leakage

Task leakage addresses structured concurrency

Structured concurrency can do the following

  • Cancel the task

    • Cancel a task when it is no longer needed
  • Tracking task

    • Track tasks while they are being executed
  • Send a wrong signal

    • An error signal when a coroutine fails indicates that an error has occurred

To define a coroutine, you must specify a CoroutineScope

It keeps track of all coroutines, and it can also cancel all coroutines started by it

Specify CoroutineScope to do structured concurrency

  • The GlobalScope lifecycle is the process level where the coroutine continues to execute even after the Activity or Fragment has been destroyed
  • MainScope can be used in an Activity to fetch the elimination coroutine in onDestory
  • ViewModelScope can only be used within a ViewModel, binding to the life cycle of the ViewModel
  • The lifecycleScope can only be used in an Activity or Fragment to bind the lifecycle of the Activity and Fragment

How to verify structured concurrency in actual combat can solve task leakage.

We can see if a coroutine is cancelled by catching an exception because a coroutine cancellation throws an exception

Android white, please give me more advice.

Content if there are mistakes, hope to get guidance