kotlin-----coroutine

What is coroutine

For the coroutine of kotlin in Android, the coroutine is a set of thread switching APIs officially provided by kotlin. It has the same function as the thread pool, but its advantage is that it writes asynchronous code in a synchronous way. . Because coroutines run on jvm, jvm does not have the concept of coroutines.

If Kotlin's coroutine is running in the current thread, it is equivalent to posting a task to the handler queue of the current thread, which will also cause blocking. Remember that if the coroutine does not switch threads, it may also cause blocking. . I will verify this later .

Okay, now start by using

If coroutine is used

In Kotlin, coroutines are not included in the standard library, so you still have to introduce them when using them:

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"

The second library is used by the android environment.

Two ways to start a coroutine:

  launch {  }
             
   async {  }

Let’s look at these two methods

launch

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

async

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

It is found that these two methods are extension functions of the CoroutineScope (coroutine scope) class, so these two functions must be called using the coroutine scope object.

The difference is that lanch will not return the running result, while async returns the Deferred object through await() of the Deferred object.

The method can obtain the execution results of the coroutine.

There is still one

withContext(Dispatchers.Main){
    
}

He is actually

async {

}.await()

abbreviation, so it will block the coroutine because the await() method is a suspending function

Explain some terms:

CoroutineScope: coroutine scope

public interface CoroutineScope {
    /**
     * The context of this scope.
     * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
     * Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
     *
     * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
     */
    public val coroutineContext: CoroutineContext
}

There is only one attribute in the coroutine scope, which is the coroutine context, which means that coroutineScope is an encapsulation of CoroutineContext . It refers to the running scope (or life cycle) of a coroutine, which is used by users to inherit and cancel the coroutine and stop operations. I can control myself

 CoroutineContext:

The context of a coroutine can be understood as the configuration information of the coroutine running, the configuration required to execute the coroutine, such as the name of the coroutine, and the thread on which the coroutine runs.

Let me take a look at the coroutineContext class. It is actually equivalent to a container, which stores various attributes, similar to hash storage.

public interface CoroutineContext {

    //重写了+号运算符
    public operator fun plus(context: CoroutineContext): CoroutineContext =
        if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
            context.fold(this) { acc, element ->
                val removed = acc.minusKey(element.key)
                if (removed === EmptyCoroutineContext) element else {
                    // make sure interceptor is always last in the context (and thus is fast to get when present)
                    val interceptor = removed[ContinuationInterceptor]
                    if (interceptor == null) CombinedContext(removed, element) else {
                        val left = removed.minusKey(ContinuationInterceptor)
                        if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
                            CombinedContext(CombinedContext(left, element), interceptor)
                    }
                }
            }

    public interface Key<E : Element>

        public val key: Key<*>

        public override operator fun <E : Element> get(key: Key<E>): E? =
            @Suppress("UNCHECKED_CAST")
            if (this.key == key) this as E else null

        public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
            operation(initial, this)

        public override fun minusKey(key: Key<*>): CoroutineContext =
            if (this.key == key) EmptyCoroutineContext else this
    }
}

So he can use it like this

Let's look at the launch function. We can pass a coroutineContext

 We can use it like this

launch(Dispatchers.Main+CoroutineName("协程名称")) {  }

CoroutineDispatcher   coroutine scheduler

Kotlin provides us with four schedulers

public actual object Dispatchers {
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
public val IO: CoroutineDispatcher = DefaultScheduler.IO
}

Default: The default scheduler, a CPU-intensive task scheduler, usually handles some simple computing tasks, or tasks with short execution time. For example, data calculation
IO: IO scheduler, IO-intensive task scheduler, suitable for performing IO-related operations. For example: network requests, database operations, file operations, etc.
Main: UI scheduler, only meaningful on the UI programming platform, used to update the UI, such as the main thread in Android. Unconfined:
unrestricted scheduler, no scheduler, currently Coroutines can run on any thread

CoroutineStart: Startup mode of coroutine

public enum class CoroutineStart {
DEFAULT,
LAZY,
ATOMIC,
UNDISPATCHED;
}

You can see that CoroutineStart is an enumeration class with four types.
DEFAULT is the default startup mode. Scheduling starts immediately after the coroutine is created. Note that it is scheduled immediately rather than executed immediately. It may be canceled before execution.
LAZY lazy startup mode will not have any scheduling behavior after creation. Scheduling will not occur until we need it to execute. Scheduling starts only when we need to manually call the Job's start, join or await functions.
ATOMIC starts scheduling immediately after the coroutine is created, but it is different from the DEFAULT mode. In this mode, after the coroutine is started, it needs to execute to the first suspension point before responding to the cancel operation.
In this mode, the UNDISPATCHED coroutine will directly start executing in the current thread until it reaches the first suspension point. It is very similar to ATOMIC, but UNDISPATCHED is very affected by the scheduler.

Job

The launch() method returns a job object, which can detect the life cycle of the coroutine and cancel the execution of the coroutine.
public interface Job : CoroutineContext.Element {
  
    public companion object Key : CoroutineContext.Key<Job>

    //判断协程是否是活的
    public val isActive: Boolean

    
   //判断协程是否完成
    public val isCompleted: Boolean

   //判断协程是否取消
    public val isCancelled: Boolean



    public fun getCancellationException(): CancellationException

    //开始协程
    public fun start(): Boolean


    //取消协程
    public fun cancel(cause: CancellationException? = null)


   

Deferred actually inherits job

Therefore, there are also methods to operate the coroutine, which also provides an important method, the await() method, to obtain the value returned by the coroutine. await is a suspension function, so when executed here, the current coroutine will be blocked. .

public interface Deferred<out T> : Job {

    public suspend fun await(): T

suspend means suspend in Chinese

The suspend function will block the current coroutine, but will not block the thread that started the coroutine.

When a suspend is declared in front of a function, the function becomes a suspending function. The suspending function can only be called in the suspending function, but this only serves as a reminder. What really plays the role of suspending is code in the function. So if you declare a suspend function, but there is no actual suspend operation in it, the compiler will gray it out for you, which means that there is no need to declare this function. So what is the use of this declaration? It is used by the creator to give a reminder to the caller, telling the caller that this function is a hanging function and may be a time-consuming function.

What the suspend function does:

When the coroutine reaches the suspended function, it will pause here. It will switch out to run the suspended function. After running the suspended function, it will switch back and run again. In fact, it is a callback + state machine mechanism.

 If you add a real suspend function provided by the system, it will be fine.

The relationship between parent coroutine and child coroutine

1. The coroutine context information in the child coroutine will be copied to the parent coroutine unless you modify it yourself.

2. After the parent coroutine ends, all the child coroutines inside it will end.

Prove the first point:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
         GlobalScope.launch(Dispatchers.Main+CoroutineName("父协程名字")) {
             Log.e("---","协程上下文-1=${this.coroutineContext}")
             launch {
                 Log.e("---","协程上下文-2=${this.coroutineContext}")
             }
             withContext(Dispatchers.IO+CoroutineName("子协程")){
                 Log.e("---","协程上下文-2=${this.coroutineContext}")
             }
         }
    }

Printed results:

E/---: Coroutine context-1=[CoroutineName(parent coroutine name), StandaloneCoroutine{Active}@d2a3906, Dispatchers.Main]
E/---: Coroutine context-3=[CoroutineName(child coroutine ), DispatchedCoroutine{Active}@8fe4bc7, Dispatchers.IO]
E/---: Coroutine context-2=[CoroutineName (parent coroutine name), StandaloneCoroutine{Active}@24715f4, Dispatchers.Main]

 We found that the configuration information of the coroutine context of coroutine 2 and the parent coroutine are the same, but coroutine 3 is different, indicating that the child coroutine will inherit the coroutine configuration information of the parent coroutine.

 CoroutineExceptionHandler coroutine exception handling

When we write code, we will definitely encounter abnormal situations. Normally we use try...catch to handle exceptions, but it is inevitable that omissions will occur. CoroutineExceptionHandler is a class that specializes in catching exceptions in coroutines. Exceptions that occur in coroutines will be caught and returned to us by the handleException method of CoroutineExceptionHandler for processing.

public interface CoroutineExceptionHandler : CoroutineContext.Element {
    public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>

    public fun handleException(context: CoroutineContext, exception: Throwable)
}

handleException will return two parameters. The first parameter is the coroutine where the exception occurred, and the second parameter is the exception that occurred.
The example is as follows: we manually throw a NullPointerException exception, and then create a CoroutineExceptionHandler and assign it to the coroutine.

val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
    Log.e("捕获异常", "${coroutineContext[CoroutineName]} :$throwable")
}

GlobalScope.launch(Dispatchers.Main + CoroutineName("主协程")+exceptionHandler) {
    Log.e("协程的coroutineContext",this.coroutineContext.toString())
    throw NullPointerException()
}
打印结果:
协程的coroutineContext: [CoroutineName(主协程), com.jf.simple.ThirdActivity$onCreate$$inlined$CoroutineExceptionHandler$1@288ff9, StandaloneCoroutine{Active}@b95b3e, Dispatchers.Main]

捕获异常: CoroutineName(主协程) :java.lang.NullPointerException


 

Problem analysis

1. If the running coroutine is running on the current thread that starts the coroutine, is it equivalent to posting a task to the thread's handler task stack?

Test code:

    @SuppressLint("MissingInflatedId")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
         GlobalScope.launch(Dispatchers.Main+CoroutineName("父协程名字")) {
              Log.e("---","111")
         }
        Log.e("---","222")
    }

Print the result:

                com.example.mytest2 E 222
                com.example.mytest2 E 111

The print result 222 is in front of 111, indicating that a task was indeed posted.

How to create a coroutine scope

We know that there are two ways to start a coroutine. In fact, there is also a kind of runBlocking, but this kind of started coroutine will block the current thread. Only when the coroutine ends, the thread that started the coroutine will end, which will cause a memory leak. , not used in general projects.

launch()

async()

They belong to the extension functions of CoroutineScope, so they must have a CoroutineScope object to call them.

method 1:

GlobalScope is a singleton of the coroutine scope, so it can be used directly
@DelicateCoroutinesApi
public object GlobalScope : CoroutineScope {
    /**
     * Returns [EmptyCoroutineContext].
     */
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}
GlobalScope.launch() {
}

Method 2:

class MainActivity : AppCompatActivity() {
    lateinit var coroutine:CoroutineScope

    @SuppressLint("MissingInflatedId")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
       coroutine=  CoroutineScope(Dispatchers.IO)  //创建一个协程作用域
        coroutine.launch { 
            Log.e("---","开始了协程")
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        coroutine.cancel() //协程取消
    }

}

But there is a problem here. If I need to open many coroutine scopes, do I have to call a lot of cancel?

As follows:

package com.example.mytest2

import android.annotation.SuppressLint
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.Settings.Global
import android.util.Log
import android.view.View
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.math.log
import kotlin.system.measureTimeMillis
import kotlin.time.measureTimedValue

class MainActivity : AppCompatActivity() {
    lateinit var coroutine:CoroutineScope
    lateinit var coroutine1:CoroutineScope

    @SuppressLint("MissingInflatedId")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
       coroutine=  CoroutineScope(Dispatchers.IO)
        coroutine.launch {
            Log.e("---","开始了协程")
        }

        coroutine1=  CoroutineScope(Dispatchers.IO)
        coroutine1.launch {
            Log.e("---","开始了协程")
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        coroutine.cancel()
        coroutine1.cancel()
    }

}

Optimization: We know that job manages coroutines, so how does CoroutineScope manage coroutines?

public fun CoroutineScope.cancel(cause: CancellationException? = null) {
    val job = coroutineContext[Job] ?: error("Scope cannot be cancelled because it does not have a job: $this")
    job.cancel(cause)
}

It was found that he actually called the job's cancel, so we created a job and let a job object manage the cancellation of multiple coroutines.



class MainActivity : AppCompatActivity() {
    val job=Job()
    @SuppressLint("MissingInflatedId")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
      
        CoroutineScope(Dispatchers.IO+job).launch {
            Log.e("---","开始了协程")
        }

         CoroutineScope(Dispatchers.IO+job).launch {
            Log.e("---","开始了协程")
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    }

}

 Pay attention to the above

CoroutineScope(Dispatchers.IO+job) is a method. It does not create an object, but a method returns an object.
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
    ContextScope(if (context[Job] != null) context else context + Job())

Method 3

Implement the CoroutineScope interface. Because there is an unimplemented property in the CoroutineScope interface, the inheritor must initialize this property. We can implement it using a delegate class.

public interface CoroutineScope {

    public val coroutineContext: CoroutineContext
}
实现CoroutineScope 的接口,协程的上下文让MainScope()方法创建的类实现实现
class MainActivity : AppCompatActivity() ,CoroutineScope by MainScope() {
    @SuppressLint("MissingInflatedId")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        launch {   //这个协程是运行在主线程的,因为mainScope创建的协程上下文中的线程是主线程
            
        }
    }
    override fun onDestroy() {
        super.onDestroy()
        cancel()
    }
}

Let’s take a look at the MainScope() method,

public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
注意这个方法创建的协程上下文的指定线程是主线程,所以说如果子协程不指定线程的话,子协程也是在主线程中运行

Way 4:

If we manage the coroutine ourselves, it will be troublesome, and sometimes we forget to cancel it, which can easily cause memory leaks, so Android provides us with some libraries.

//第四种: android提供的和lifecycle相关的库
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
lifecycleScope.launch {  } 用在activity和fragment中

viewModelScope.launch{} 用在viewModel中
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
Official website address

Analysis of the principles of coroutines

The non-blocking principle of coroutines is actually implemented using the callback + state machine mechanism.

Reference article:

Kotlin syntax advancement - Coroutine (2) Principle of suspending function_Flying over the town's blog at that time-CSDN blog

suspend: means to suspend
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_coroutine)
        val launch = GlobalScope.launch(Dispatchers.Main) {   //实质上相当于往主线程中post了一个新任务,这个任务就是{}闭包的内容

            println("thread name= ${Thread.currentThread().name}")
            println("======12")
            delay(500)
            println("======23")
        }
        println("--end--")
    }


Let’s take a look at the launch function:

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit   //执行的也是也是我们传入好的闭包,只是给它改成了挂起函数
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

Analyze this code:

class CoroutineActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_coroutine)

        
        运行到挂起函数的时候,相当于线程执行到这里切断了,一部分去执行挂起函数,一部分去按顺序去执行
        val launch = GlobalScope.launch(Dispatchers.Main) {  //挂起函数
            println("thread name= ${Thread.currentThread().name}")
            println("======12")
            delay(500)
            println("======23")
        }
        println("--end--")  //接下来的内容
    }
}

It should be noted that the suspend keyword is only a prompt and cannot play a suspending role. The suspending methods include withcontent(), launch, etc.

[Mashang School Starts] The suspension of Kotlin coroutines is so magical and difficult to understand? Today I peeled off its skin_bilibili_bilibili code starts school (kaixue.io): Kotlin coroutine issue 2. If you have any thoughts after reading this, please leave a message to discuss! https://www.bilibili.com/video/BV1KJ41137E9?spm_id_from=333.999.0.0

Guess you like

Origin blog.csdn.net/xueyoubangbang/article/details/123095882