kotlin-----协程

什么是协程

对于android中kotlin的协程来说,协程就是由kotlin 官方提供的一套线程切换的API,和线程池的功能是一样的,只不过他的优势在于用同步的方式写出了异步的代码。因为协程是运行在jvm上的,jvm没有协程这个概念。

kotlin的协程如果是运行在当前的线程,相当于往当前的线程的handler队列中,post了一个任务,也会造成阻塞,记住如果协程没有切换线程的话,那个有可能也会造成阻塞。这一点我待会会验证

好,现在开始从使用

协程如果使用

在kotlin中协程并没有被纳入标准库,所以使用的时候还是得引入:

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

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

第二个库是android环境使用的。

开启协程的两个方法:

  launch {  }
             
   async {  }

我们看下这两个方法

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
}

发现这两个方法都是 CoroutineScope(协程作用域)类的扩展函数,所以说这两个函数必须得在用协程作用域对象才能调用。

区别在于,lanch不会返回运行结果,而async返回的Deferred对象,通过Deferred对象的await()

方法可以获得协程的执行结果。

还有一个

withContext(Dispatchers.Main){
    
}

他其实是

async {

}.await()

的简写,所以会阻塞协程,因为await()方法是挂起函数

解释下几个名词:

CoroutineScope:协程作用域

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
}

协程作用域中就一个属性,就是协程上下文,说明协程coroutineScope是对CoroutineContext的一个封装。 是指一个协程的运行范围(或者说生命周期)用来让用户继承,来实现取消协程和停止操作。自己可以控制

 CoroutineContext:

协程的上下文可以理解为协程运行的配置信息,执行协程需要的那个配置,比如:协程的名字,协程运行在那个线程上。

我看看下coroutineContext类,其实就相当于一个容器,里面存放这各种属性,类似于hash那种存储。

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
    }
}
CoroutineContext即协程的上下文,是 Kotlin 协程的一个基本结构单元。巧妙的运用协程上下文是至关重要的,以此来实现正确的线程行为、生命周期、异常以及调试。它包含用户定义的一些数据集合,这些数据与协程密切相关。它是一个有索引的 Element 实例集合。这个有索引的集合类似于一个介于 set 和 map之间的数据结构。每个 element 在这个集合有一个唯一的 Key 。当多个 element 的 key 的引用相同,则代表属于集合里同一个 element。它由如下几项构成:

Job: 控制协程的生命周期;
CoroutineDispatcher: 向合适的线程分发任务;
CoroutineName: 协程的名称,调试的时候很有用;
CoroutineExceptionHandler: 处理未被捕捉的异常。
CoroutineContext 有两个非常重要的元素 — Job 和 Dispatcher,Job 是当前的 Coroutine 实例而 Dispatcher 决定了当前 Coroutine 执行的线程,还可以添加CoroutineName,用于调试,添加 CoroutineExceptionHandler 用于捕获异常,它们都实现了Element接口。看一个例子:

所以他可以这样用

我们看launch函数,可以传一个coroutineContext

 我们可以这样使用

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

之前一直不了解为什么还能Dispatchers.Main+CoroutineName("协程名称")这样写,
原来他重写了+的操作符Plus

CoroutineDispatcher  协程调度器

kotlin给我们提供了四种调度器

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:默认调度器,CPU密集型任务调度器,通常处理一些单纯的计算任务,或者执行时间较短任务。例如数据计算
IO:IO调度器,IO密集型任务调度器,适合执行IO相关操作。比如:网络请求,数据库操作,文件操作等
Main:UI调度器,只有在UI编程平台上有意义,用于更新UI,例如Android中的主线程
Unconfined:非受限调度器,无所谓调度器,当前协程可以运行在任意线程上

CoroutineStart:协程的启动模式

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

可以看到CoroutineStart是一个枚举类,有四种类型。
DEFAULT默认启动模式,协程创建后立即开始调度,注意是立即调度而不是立即执行,可能在执行前被取消掉。
LAZY懒汉启动模式,创建后不会有任何调度行为,直到我们需要它执行的时候才会产生调度。需要我们手动的调用Job的start、join或者await等函数时才会开始调度。
ATOMIC 在协程创建后立即开始调度,但它和DEFAULT模式是有区别的,该模式下协程启动以后需要执行到第一个挂起点才会响应cancel操作。
UNDISPATCHED协程在这种模式下会直接开始在当前线程下执行,直到运行到第一个挂起点。和ATOMIC很像,但UNDISPATCHED很受调度器的影响

Job

launch()方法返回的是一个job对象,这个对象可以检测协程的生命周期,也可以取消协程的执行
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  实际上是继承job

所以也有对协程进行操作的方法,其中还提供了 一个重要的方法,await()方法,获得协程返回的值,await是一个挂起函数,所以执行到这里的时候会阻塞当前的协程。

public interface Deferred<out T> : Job {

    public suspend fun await(): T

suspend 中文 暂停的意思

suspend函数会阻塞当前的协程,但是不会阻塞开启协程的线程

在函数的前面申明一个suspend的时候,这个函数就变成了挂起函数,挂起函数只能在挂起函数中调用,但是这个只是起到一个提醒的作用,真正起到挂起作用的是函数中的代码。所以说如果你申明一个挂起函数,但是里面没有实际的挂起操作,编译器会给你置灰,意思是告诉你这个函数没必要申明。那么要这个申明有什么用呢?用于创建者给调用者一个提醒,告诉调用者说这个函数是一个挂起函数,可能是一个耗时的函数

挂起函数作用:

协程运行到挂起函数的时候,会暂停到这,切出去运行挂起的函数,运行完挂起函数后,再切回来重新运行,其实就是一个callback+状态机的机制

 如果里面加上一个系统提供的真正的挂起函数就没事了

父协程和子协程的关系

1.子协程中的协程上下文的信息会copy父协程,除非自己修改

2.父协程结束后,他里面的子协程都会结束

证明第一点:

    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}")
             }
         }
    }

打印的结果:

E/---: 协程上下文-1=[CoroutineName(父协程名字), StandaloneCoroutine{Active}@d2a3906, Dispatchers.Main]
E/---: 协程上下文-3=[CoroutineName(子协程), DispatchedCoroutine{Active}@8fe4bc7, Dispatchers.IO]
E/---: 协程上下文-2=[CoroutineName(父协程名字), StandaloneCoroutine{Active}@24715f4, Dispatchers.Main]

 我们发现协程协程2和父协程的协程上下文的配置信息是一样的,而协程3不一样,说明子协程会继承父协程的协程配置信息

 CoroutineExceptionHandler 协程异常处理

我们在写代码的时候,肯定会遇到异常的情况,正常我们处理异常使用try …catch捕获,但难免会出现遗漏。CoroutineExceptionHandler 就是协程专门捕获异常的类,协程中出现的异常都会被捕获并由CoroutineExceptionHandler的handleException方法返回给我们进行处理。

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

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

handleException会返回两个参数,第一个参数是出现异常的协程,第二个参数是出现的异常。
示例如下:我们手动抛出一个NullPointerException异常,然后创建一个CoroutineExceptionHandler赋给协程。

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


 

问题解析

1.如果运行的协程就是运行在当前的开启协程的线程是相当于post一个任务到线程的handler任务栈吗?

测试代码:

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

打印结果:

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

打印结果 222却在111的前面,说明确实是post了一个任务

如何创建一个协程作用域

我们知道开启协程的方法有两种,其实还有一种 runBlocking,但是这种开启的协程会阻塞当前的线程,只有当协程结束后,开启协程的线程才会结束,会造成内存泄露,一般项目中不使用。

lanch()

async()

他两属于CoroutineScope的扩展函数,所以必须的有CoroutineScope的对象才能调用

方法1:

GlobalScope 就是一个协程作用域的单例,所以可以直接使用
@DelicateCoroutinesApi
public object GlobalScope : CoroutineScope {
    /**
     * Returns [EmptyCoroutineContext].
     */
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}
GlobalScope.launch() {
}

方法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() //协程取消
    }

}

但是这里有一个问题,假如我需要开启很多协程作用域,我是不是得调用很多cancel

如下所示:

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()
    }

}

优化:我们知道job是管理协程的,那么CoroutineScope是如何管理协程的呢?

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)
}

发现他其实也是调用了job的cancel,所以我们创建一个job,让一个job对象管理多个协程的取消



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()
    }

}

 要注意上面的

CoroutineScope(Dispatchers.IO+job)是一个方法,不是创建一个对象,而是一个方法返回一个对象
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
    ContextScope(if (context[Job] != null) context else context + Job())

方法3

实现CoroutineScope 接口,因为CoroutineScope 接口中有一个未实现的属性,所以继承者得初始化这个属性,我们可以采用委托类的方式实现

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()
    }
}

我们看下MainScope()方法,

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

方式4:

如果我们自己去管理协程的话,感觉麻烦,有时候又会忘记取消,容易造成内存泄露,所以android为我们提供了一些库

//第四种: 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'
官网地址

协程的原理解析

协程的非阻塞原理其实就是利用 callback + 状态机机制实现的

借鉴文章:

kotlin语法进阶 - 协程(二)挂起函数原理_飞过那时的城镇的博客-优快云博客

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--")
    }


我们看下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
}

分析这个代码:

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--")  //接下来的内容
    }
}

要注意的是:suspend 关键字只是一个提示的作用,不能起到挂起的作用,有挂起作用的方法有withcontent(),launch等

【码上开学】Kotlin 协程的挂起好神奇好难懂?今天我把它的皮给扒了_哔哩哔哩_bilibili码上开学 ( kaixue.io ) 之:Kotlin 的协程第 2 期。看完有啥想法,来留言讨论啊!icon-default.png?t=O83Ahttps://www.bilibili.com/video/BV1KJ41137E9?spm_id_from=333.999.0.0

在Kotlin协程中,默认情况下,如果一个协程在同一个作用域内失败(抛出未捕获的异常),它会取消该作用域内的所有其他协程。这是因为协程的默认异常处理策略是传播异常并取消其父作用域。这种行为有助于防止资源泄漏,但在某些情况下,你可能希望一个协程的失败不会影响其他协程。

为了避免一个子协程的崩溃导致所有兄弟协程都崩溃,你可以使用 `SupervisorJob`。`SupervisorJob` 是一种特殊的 `Job`,它允许其子协程独立运行,即一个子协程的失败不会取消其他子协程。

以下是一个示例,演示如何使用 `SupervisorJob` 来实现这一点:

```kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    // 创建一个 SupervisorJob
    val supervisor = SupervisorJob()

    // 创建一个 CoroutineScope,使用 SupervisorJob
    val scope = CoroutineScope(coroutineContext + supervisor)

    // 启动多个子协程
    scope.launch {
        println("Child 1 is running")
        delay(1000)
        println("Child 1 completed")
    }

    scope.launch {
        println("Child 2 is running")
        delay(500)
        throw RuntimeException("Child 2 failed")
    }

    scope.launch {
        println("Child 3 is running")
        delay(1500)
        println("Child 3 completed")
    }

    // 等待一段时间,观察输出
    delay(2000)

    // 取消作用域
    scope.cancel()
}
```

在这个示例中:

1. 创建了一个 `SupervisorJob`。
2. 使用 `SupervisorJob` 创建了一个 `CoroutineScope`。
3. 在这个作用域内启动了多个子协程。
4. 即使 `Child 2` 抛出异常,其他子协程(`Child 1` 和 `Child 3`)仍然会继续运行,不会被取消。

输出结果将会是:

```
Child 1 is running
Child 2 is running
Child 3 is running
Child 2 failed
Child 1 completed
Child 3 completed
```

可以看到,尽管 `Child 2` 失败了,`Child 1` 和 `Child 3` 仍然能够正常完成。

通过使用 `SupervisorJob`,你可以确保一个子协程的失败不会影响其他兄弟协程,从而实现更细粒度的错误处理和协程管理。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值