Coroutines in Kotlin

Before we talk about coroutines, a little bit about Kotlin

Kotlin

Kotlin was developed by JetBrain in 2011;

At GoogleIO 2017 it was announced that Kotlin would be the first language for Android app development;

At IO 2019, Kotlin announced the slogan “Kotlin First”, and Kotlin has had its moment in Android app development since then.

Definition of coroutines in Kotlin

Coroutines were introduced in kotlin version 1.3;

The coroutine in Kotlin is a set of thread framework apis provided by Kotlin, like the Java Executor and Android AsyncTask apis.

It is also mentioned in the official Android documentation: Coroutines are our recommended solution for asynchronous programming on Android;

So with Executor and the AsyncTask family of apis, why does Android officially say it is the recommended solution for asynchronous programming?

What’s good about coroutines in Kotlin
Before we start – closures

I’ll talk a little bit about closures before, because it’s a concept that comes into play in the use of coroutines;

We also use closures a lot when we use Kotlin’s API;

Closures are not unique to Kotlin and have been around since Java8;

Let’s use Thread as an example

// Create a Thread that internally passes a Runnable anonymous inner class object
After Java8, we called the Single Method interface SAM (Single Abstract Method) interface
Thread(object:Runnable{
  override fun run(a){
    // ....doWork....}})// also after Java8 we can use lambda to simplify SAM interface writing
Thread({
  // ....doWork....
})
In Kotlin there is a syntactic sugar: when the last argument to a function is a Lambda expression, the Lambda can be written outside the parentheses. This is Kotlin's closure principle
Thread{
  / /... doWork ....
}
Copy the code
Basic use
// Method 1: The coroutine opened in this way is blocked and cannot be used in actual services.
runBlocking{
  
}
// Use GlobalScope to launch the coroutine
// Coroutines opened in this way do not block threads. But it has the same life cycle as the app.
GlobalScope.launch{
  
}
// Create a coroutine object using the CoroutineScope
Context is a CoroutineContext parameter
// It is generally recommended to use this method to manage and control the life cycle of coroutines by passing context parameters;
// The context can be understood as the context of the coroutine
The code wrapped in launch is the coroutine we want to launch
val coroutineScope = CoroutineScope(context)
cortineScope.launch{
  / /... doWork ....
}
Copy the code
What’s good about it

Before we jump to conclusions, let’s look at the following two scenarios

Scenario 1: We get a student information from the Server:

private fun getData(a){
    getStudentInfo(object :InfoCallback{
        override fun onSuccess(student:Student) { 
          // Get the student information
        }
        override fun onFailure(a){}})}Copy the code

So, the product is going to iterate, the requirements are going to change, and this is scenario two

Scenario 2: After I get the student’s information, I need to query which class she/he is in?

private fun getData(a){
    getStudentInfo(object :InfoCallback{
        override fun onSuccess(student:Student) {
						// Query class information based on student information
            getClassInfo(student:Student,object :InfoCallback{
        		override fun onSuccess(class:Class) {
            		// Get the class information
        			}
       			override fun onFailure(a){}})}override fun onFailure(a){}})}Copy the code

So how do we write coroutine code

private fun getData(a){
  // Create a CoroutineScope object
  val coroutineScope = CoroutineScope(Dispatchers.Main)
  // Call the launch method to launch a coroutine, where Dispatchers.Mian specifies that our coroutine is running in the main thread
  cortineScope.launch(Dispatchers.Main){
    // Async starts a new Coroutine that returns Coroutine that implements the Deferred interface
    val student = async { getStudent() }
    val class   = async { getClass(student.await()) }
    // To get the class information, call deferred.await() to get the method return value
    val class1  = class.await()
    tv.text = class1.name
  }
}

/** * get student information */
private suspend fun getStudent(a): Student {
    withContext(Dispatchers.IO){
      delay(100)
      return "zhangqi"}}/** * Get class information */
private suspend fun getClass(a): Class {
    withContext(Dispatchers.IO){
      delay(100)
      return "Class"}}Copy the code

How to do not have contrast to do not hurt!!

We’re probably used to using callbacks and we don’t see anything wrong with that, but compared to the Kotlin coroutine implementation

This kind of callback nested callback, is not a bit bad code readability, bad maintenance;

So what’s good about coroutines:

Made our code more readable and maintainable;

Most importantly: write asynchronous code as synchronous code, which is the core competitiveness of Kotlin coroutines compared to previous multithreaded frameworks;

So how can coroutines be written like this? Let’s look at the code above

  1. Async {} and await are used
  2. We use the suspend keyword in the functions we define, and suspend decorates the methods we call suspend functions

Next, let’s talk about suspend functions

Hang up function

Suspension is the action of automatically cutting threads and cutting back

Custom suspend functions use the suspend keyword

Suspend functions can only be scheduled by coroutines or other suspend functions
suspend fun method(a){
  withContext(Dispatchers.IO){
    
  }
}
Copy the code

After launching a coroutine using launch or async, suspend is called from the coroutine

When our suspend function is finished, the coroutine will help us cut the thread back

When our coroutine is running on the main thread, when the suspend function is called, thread switching may occur according to the Dispatchers. When our code is finished, the coroutine will automatically cut back to the original thread.

This action is called resume in Kotlin, which is a characteristic of coroutines;

So suspended functions are called either in other suspended functions or in coroutines; Why? Because it cuts through and it cuts back!

The suspend keyword does not suspend. It simply serves as a reminder to the caller that this function is going to be a time-consuming one. Call me from the coroutine.

When do I need to suspend a function

Perform time-consuming tasks

How to write
suspend fun method(a){
  // withContext is also a suspend function decorated internally by the suspend keyword
	withContext(Dispatchers.IO){
    // In an IO thread, time consuming tasks are performed}}Copy the code

In Kotlin the suspension is non-blocking, right?

Non-blocking suspend

For non-blocking suspension we can split [non-blocking] [suspension]

[Non-blocking]

Non-blocking is relative to blocking;

The blocking of threads can be taken as an example of something in our life:

You’re driving down the road, your car breaks down, you stop behind the driver (you’re doing a time-consuming task),

When the car is fixed and ready to go (the time-consuming task is over);

Or you can move the car to another road where there are no cars and wait for the car to be repaired (open another thread to perform your task).

In fact, non-blocking is a description of the characteristics of the suspend action; Suspending itself is an operation that automatically cuts the thread and cuts back, so it doesn’t block the UI thread; Compared to the previous thread framework this will automatically switch back;

In our code, we seem to be writing code synchronously, but we rely on coroutines to accomplish asynchronous tasks.

Conclusion a wave

Coroutines are cutting threads;

A hang is a cut thread that automatically cuts back

Pending non-blocking is completing a non-blocking task in a manner that appears to block