Kotlin 协程基础使用学习

这篇具有很好参考价值的文章主要介绍了Kotlin 协程基础使用学习。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

原文: Kotlin 协程基础使用学习-Stars-One的杂货小窝

本篇阅读可能需要以下知识,否则可能阅读会有些困难

  • 客户端开发基础(Android开发或JavaFx开发)
  • Java多线程基础
  • kotlin基础

本文尽量以使用为主,以代码为辅讲解,不提及过深协程底层代码逻辑,仅做一个基础入门来快速上手学习(断断续续写了好几个周,若是有错误之处也请在评论区提出 😂)

协程优点

首先,先说下为什么使用协程吧

协程得和线程进行比较

  • 可在单个线程运行多个协程,其支持挂起,不会使运行协程的线程阻塞。
  • 协程可以取消
  • 协程可以让异步代码同步化,其本质是轻量级线程,进而可以降低异步程序的设计复杂度。

对于客户端的网络请求数据,以往写法都是在回调操作里进行更新UI操作,一旦业务复杂,且需要调用多个接口

如接口A调用完后得到的数据A需要进行拼接,从而构造成接口B的参数,去请求接口B得到数据,那么就得在里面疯狂套娃,难管理且阅读很难受

而采用协程,则将异步操作变为同步操作,如下图:

Kotlin 协程基础使用学习

需要注意的是,并不是说协程比线程池进行并发任务性能更好,实际上协程内部还是使用线程调度的那一套,只不过对于开发者来说,更是黑箱操作

只是对于客户端开发来说,可以从那种回调处理更新UI的解放出来

从性能上去看:

协程的性能并不优于线程池或者其他异步框架,主要是其做了更多语言级别步骤,但通常情况下,与其他框架的性能几乎一致,因为相比IO的耗时,语言级别的损耗可以几乎忽略不计;

从设计模式去看:

协程使得开发者可以自行管理异步任务,而不同于线程的抢占式任务,并且写成还支持子协程的嵌套关闭、更简便的异常处理机制等,故相比其他异步框架,协程的理念更加先进;

入门使用

依赖说明

kotlin的协程是一个单独的库,需要我们进行依赖后才能使用

这里需要说明一下,协程分为了几个Module,需要根据情况引用(我这里只介绍其中几个常用的模块,需要了解更多可以去看官方文档说明)

  • kotlinx-coroutines-core
  • kotlinx-coroutines-core-jvm
  • kotlinx-coroutines-android
  • kotlinx-coroutines-javafx

kotlinx-coroutines-core模块是针对多平台项目一个公共库,Kotlin/Native、Kotlin/JVM 和 Kotlin/JS 上使用。

kotlinx-coroutines-core-jvm是专门为在 JVM 平台上运行的项目设计,并提供了一些额外的功能,比如提供针对 JVM 的调度器和扩展函数。

kotlinx-coroutines-androidkotlinx-coroutines-javafx则是针对的特定的UI平台,提供了对应的调度器,Android是Dispatcher.Main,JavaFx则是Dispatch.JavaFx(实际上也能用Dispatch.Main,与Dispatch.JavaFx等同的)

PS: 这里如果不懂Dispatchers,没有关系,只需要记住这个就是方便我们切换到UI线程(主线程)操作即可

像我一般是在Android平台或者是JavaFx平台,没有JS和Native的需求

所以一般引用kotlinx-coroutines-core-jvm即可,会自动将kotlinx-coroutines-core也引入

之后根据平台选择kotlinx-coroutines-androidkotlinx-coroutines-javafx依赖

引入依赖(示例):

<dependency>
    <groupId>org.jetbrains.kotlinx</groupId>
    <artifactId>kotlinx-coroutines-core-jvm</artifactId>
    <version>1.8.0-RC2</version>
</dependency>

//这里省略了对应平台的版本依赖,参考下面gradle依赖即可

gradle引入:

implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0-RC2")


//对应平台版本依赖,版本是一致的,如果想要切换到主线程来更新UI操作,就需要下面的依赖
//android
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0-RC2")

//javafx
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:1.8.0-RC2")

//注意还得加入插件
plugins {
    // For build.gradle.kts (Kotlin DSL)
    kotlin("jvm") version "1.9.21"
    
    // For build.gradle (Groovy DSL)
    id "org.jetbrains.kotlin.jvm" version "1.9.21"
}

不过需要注意的是,上面的版本仅供参考

因为协程依赖与kotlin版本有关联关系,如果你使用协程库的高版本,可能kotlin也要使用较高版本,不然可能编译会报错

对于maven项目,修改项目使用的kotlin版本即可

对于gradle项目,除了修改kotlin版本,还得修改上面的那个plugin插件版本

不过稳妥的做法,还是根据kotlin版本选择对应的协程版本,毕竟没准kotlin版本一升级,整个项目就跑不起来,尤其是Android项目(kotlin版本依赖比较严重)

协程与kotlin版本对应关系见下表(点击展开)
发布时间 kotlin版本 官方推荐的协程库版本 标准库更新版本简述
2020-04-15 1.3.72 1.3.8 Kotlin 1.3.70 的错误修复版本。
2020-08-17 1.4.0 1.3.9 具有许多功能和改进的功能版本,主要关注质量和性能。
2020-09-07 1.4.10 1.3.9 Kotlin 1.4.0 的错误修复版本。
2020-11-23 1.4.20 1.4.1 支持新的 JVM 功能,例如通过调用动态进行字符串连接,改进了 KMM 项目的性能和异常处理,DK 路径的扩展:Path(“dir”) / “file.txt”
2020-12-07 1.4.21 1.4.1 Kotlin 1.4.20 的错误修复版本
2021-02-03 1.4.30 1.4.2 新的 JVM 后端,现在处于 Beta 版;新语言功能预览;改进的 Kotlin/Native 性能;标准库 API 改进
2021-02-25 1.4.31 1.4.2 Kotlin 1.4.30 的错误修复版本
2021-03-22 1.4.32 1.4.3 Kotlin 1.4.30 的错误修复版本
2021-05-05 1.5.0 1.5.0-RC 具有新语言功能、性能改进和进化性更改(例如稳定实验性 API)的功能版本。
2021-05-24 1.5.10 1.5.0 Kotlin 1.5.0 的错误修复版本。
2021-06-24 1.5.20 1.5.0 默认情况下,通过 JVM 上的调用动态进行字符串连接;改进了对 Lombok 的支持和对 JSpecify 的支持;Kotlin/Native:KDoc 导出到 Objective-C 头文件和更快的 Array.copyInto() 在一个数组中;Gradle:缓存注解处理器的类加载器并支持 --parallel Gradle 属性;跨平台的 stdlib 函数的对齐行为
2021-07-13 1.5.21 1.5.0 Kotlin 1.5.20 的错误修复版本。
2021-08-23 1.5.30 1.5.1 JVM上注解类的实例化;改进的选择加入要求机制和类型推断;测试版中的 Kotlin/JS IR 后端;支持 Apple Silicon 目标;改进的 CocoaPods 支持;Gradle:Java 工具链支持和改进的守护程序配置;
2021-09-20 1.5.31 1.5.2 Kotlin 1.5.30 的错误修复版本。
2021-11-29 1.5.32 1.5.2 Kotlin 1.5.31 的错误修复版本。
2021-11-16 1.6.0 1.6.0 具有新语言功能、性能改进和进化性更改(例如稳定实验性 API)的功能版本。
2021-12-14 1.6.10 1.6.0 Kotlin 1.6.0 的错误修复版本。
2022-04-04 1.6.20 1.6.0 具有各种改进的增量版本
2022-04-20 1.6.21 1.6.0 Kotlin 1.6.20 的错误修复版本。
2022-06-09 1.7.0 1.7.0 在 Alpha for JVM 中发布了 Kotlin K2 编译器的功能、稳定的语言功能、性能改进和演进性变化,例如稳定实验性 API。
2022-07-07 1.7.10 1.7.0 Kotlin 1.7.0 的错误修复版本。
2022-09-29 1.7.20 1.7.0 具有新语言功能的增量版本,支持 Kotlin K2 编译器中的多个编译器插件,默认启用新的 Kotlin/Native 内存管理器,以及对 Gradle 7.1 的支持。
2022-11-09 1.7.21 1.7.0 Kotlin 1.7.20 的错误修复版本。
2022-12-28 1.8.0 1.7.0 一个功能版本,改进了 kotlin-reflect 性能、新的 JVM 递归复制或删除目录内容实验功能、改进了 Objective-C/Swift 互操作性。
2023-02-02 1.8.10 1.7.0 Kotlin 1.8.0 的错误修复版本。
2023-04-03 1.8.20 1.7.0 功能发布,包括 Kotlin K2 编译器更新、AutoCloseable 接口和 stdlib 中的 Base64 编码、默认启用的新 JVM 增量编译、新的 Kotlin/Wasm 编译器后端。
2023-04-25 1.8.21 1.7.0 Kotlin 1.8.20 的错误修复版本。
2023-06-08 1.8.22 1.7.0 Kotlin 1.8.20 的错误修复版本。
2023-07-06 1.9.0 1.7.0 包含 Kotlin K2 编译器更新的功能版本、新的枚举类值函数、开放式范围的新运算符、Kotlin Multiplatform 中的 Gradle 配置缓存预览、Kotlin Multiplatform 中的 Android 目标支持更改、Kotlin/Native 中的自定义内存分配器预览 。

摘自:kotlin标准库与kotlin协程相关支持库对应关系(持续更新。。。)_kotlinx-coroutines-core和kotlin-gradle-plugin版本对应-CSDN博客

协程启动

先来一段协程启动的代码

fun main() {
    runBlocking {
        val scope = this
		//启动协程
        val job = scope.launch(Dispatchers.IO) {
            delay(1000)
            println("延迟1s后打印")
        }
        println("已启动协程了")
    }
}

运行结果:

已启动协程了
延迟1s后打印

协程取消

fun main() {
    runBlocking {
        val scope = this
        val job = scope.launch(Dispatchers.IO) {
            delay(1000)
            println("延迟1s后打印")
        }
        println("已启动协程了")
		job.cancel()
		println("已取消协程")
    }
}

基础概念

协程主要包含以下部分:

  • 协程Job:协程Job是协程的执行单元,它表示了一个协程的任务。我们可以通过Job来控制协程的启动、取消、等待和异常处理等操作。
  • 协程构建器(Coroutine Builders):协程构建器是创建协程的入口点。在Kotlin中,常见的协程构建器有launchasyncrunBlocking等。
  • 协程作用域(Coroutine Scope):协程作用域是协程的生命周期范围。它定义了协程的生命周期和取消操作。通常,我们会使用GlobalScopeCoroutineScope等来创建协程作用域。
  • 协程执行器(Dispatcher):协程执行器(也称为调度器)是协程的执行线程(或线程池)。它决定了协程在哪个线程上执行,可以通过指定不同的调度器来实现协程的并发和异步操作。
  • 协程挂起函数(Suspending Function):协程挂起函数是在协程中使用的特殊函数,它可以暂时挂起协程的执行而不阻塞线程。挂起函数使用suspend修饰符,并可调用其他挂起函数、阻塞函数、异步函数等。
  • 协程上下文(Coroutine Context):协程上下文是协程的运行环境,包含了协程的调度器(Dispatcher)和其他上下文元素,如异常处理器等。协程上下文可以由调度器、Job、异常处理器等元素组成。

其实入门的简单使用,用的比较频繁的还是前5个概念,最后一个协程上下文概念我可能不会花太多笔墨写

协程上下文CoroutineContext实际是一个接口,而Job,Dispatcher都是实现了协程上下文此接口

首先,要有个概念,只要在协程作用域中才能启动协程,而协程作用域,需要通过协程构建器来进行创建

我们来看上面的代码

fun main() {
	//runBlocking方法实际上就是协程构建器
    runBlocking {
		//这里的作用域实际就是协程作用域
        val scope = this
		//通过launch方法来启动一个协程,得到一个Job对象
		//实际上,把Job对象说成协程应该就好理解了
		//注意这里,出现了一个Dispatchers.IO,这个就是我们的协程执行器,可以看做为一个协程提供的线程池(之后会细讲)
        val job = scope.launch(Dispatchers.IO) {
			//delay是延迟执行,是协程作用域提供的一个方法
            delay(1000)
            println("延迟1s后打印")
        }
        println("已启动协程了")
    }
}

通过上面的代码,应该对前4个概念有些基本了解了,再来说说挂起函数

以上面代码为例,协程里的方法太多了,想要封装成一个方法,可以这样改造:

fun main() {
    runBlocking {
        val scope = this
        val job = scope.launch(Dispatchers.IO) {
           test()
        }
        println("已启动协程了")
    }

}

suspend fun test() {
    delay(1000)
    println("延迟1s后打印")
}

由于我们因为用到了delay这个方法,所以我们得将当前方法加上一个suspend关键字,声明当前函数是挂起函数

只有声明了我们才能在函数里使用delay这个方法,不加关键字,IDE会提示行代码标红,无法通过编译

同时,还有一个概念,只有在协程作用域上,才能调用挂起函数

当然,如果你的方法里没有delay此类方法,可以不加suspend关键字声明

协程作用域提供了不止delay这个方法,还有些其他方法,下文会进行补充

至于最后一个协程上下文,我们可以runBlocking和launch方法参数见到它的身影如下图:

Kotlin 协程基础使用学习

由于本文偏向使用为主,所以不打算对协程上下文进行展开细说了

协程构建器

前面也说到了,runBlocking()可以看做为一个协程构建器,但这个只是方便我们在main方法或者测试使用,为什么呢?

因为它实际上会阻塞当前线程,如下代码:

fun main() {
    runBlocking() {
        val scope = this
        val job = scope.launch(Dispatchers.IO) {
           delay(1000)
           println("延迟1s后打印")
        }
        println("已启动协程了")
    }
    println("任务结束")
}

输出结果:

已启动协程了
延迟1s后打印
任务结束

由输出结果可以看出,当前main方法需要等待runBlocking()方法及里面协程执行完毕才会执行完毕

但是像Android开发和Javafx开发,如果想上述这样写法,在runBlocking()进行耗时长的任务,那么估计UI线程直接卡死,Android直接出现ANR异常了

那么问题来了,协程提供了哪些协程构造器?

答案如下:

  • runBlocking
  • launch
  • async

runBlocking: 会创建一个新的协程同时阻塞当前线程,直到协程结束。适用于main函数和单元测试

需要注意的是,runBlocking会根据最后一行从而返回数值,类似kotlin对象的run函数,如

fun main() {
    val str = runBlocking() {
		//省略协程启动等操作
        "hello"
    }
	//返回字符串
    println(str)
}

launch : 创建一个新的协程,不会阻塞当前线程,必须在协程作用域中才可以调用。它返回的是一个该协程任务的引用,即Job对象。这是最常用的启动协程的方式。

async: 创建一个新的协程,不会阻塞当前线程,必须在协程作用域中才可以调用,并返回Deffer对象。可通过调用Deffer.await()方法等待该子协程执行完成并获取结果。常用于并发执行-同步等待和获取返回值的情况。

由于launchasync2个构造器得需要和协程作用域配合使用,所以决定在下面和协程作用域一起讲解了

协程和协程作用域

协程作用域

如果在一段普通代码想要开启协程,除了上面说到的runBlocking方法,我们还可以通过协程作用域来调用launchasync来进行协程的启动

可用的协程作用域有:

  • GlobalScope
  • CoroutineScope
  • supervisorScope{} 好像低版本只有方法,而高版本的协程库则可以使用类SupervisorScope
  • MainScope 主线程协程作用域(需要引用对应平台的依赖,如android或javafx才会有此作用域)

其中GlobalScope是一个全局的协程作用域对象,使用的话,直接使用静态方法来进行,如下代码:

GlobalScope.launch { 
	//你的逻辑
}

不过这种启动的协程存在组件被销毁但协程还存在的情况,一般不推荐

而一般推荐使用新建一个CoroutineScope对象来启动协程,之后在组件销毁的生命周期手动调用cancel()方法,会将当前所有的协程任务都取消,如下代码:


//在当前类声明此对应(如Activity)
val scope = CoroutineScope(Dispatchers.Main)

//这里在按钮点击事件里执行
//这里使用的协程调度器指定当前协程作用域是在主线程(UI线程)
scope.launch{
	
}

//在组件销毁的生命周期(如Activity的onDestroy方法里)
scope.cancel()

SupervisorScope这个协程作用域主要是针对异常的,如果子协程发生异常,则不影响父协程的运行(具体可见下文的"协程里的异常"一章),这里先不介绍

MainScope主要是UI主线程的协程作用域,在此作用域,相当于在主线程操作,一般我们将耗时操作切换到Dispatchers.IO去做,如下代码:

MainScope().launch{
	withContext(Dispatchers.IO){
		//网络请求等耗时操作
	}
	//更新UI操作
}

上面的withContext()方法也是在协程作用域才能使用的方法,目的就是切换到其他协程执行耗时操作,执行完毕后再切换回当前的协程(主线程),是个阻塞操作

如果需要根据网络请求的结果从而来进行更新UI,可以利用withContext()的返回值,如将上述代码改造如下:

MainScope().launch{
	val str = withContext(Dispatchers.IO){
		//网络请求等耗时操作
		//假设得到一个字符串返回值
		"hello"
	}
	//更新UI操作
	tv.text = str
}

PS:如果对于Android平台,还可以使用下面的2个作用域:

  • lifecycleScope:生命周期范围,用于activity等有生命周期的组件,在DESTROYED的时候会自动结束,需要导入依赖implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
  • viewModelScope:viewModel范围,用于ViewModel中,在ViewModel被回收时会自动结束,需要导入依赖implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'

介绍完上面几个协程作用域后,接下来对launch方法和async方法进行讲解

调度器

在讲解launch方法之前,先讲解下调度器的种类和概念

首先,我们知道此launch方法返回job对象,之后我们可以对job对象操作,调用job.cancel()取消任务

而launch方法里的传参,可以传递一个调度器,那么协程中有哪几个调度器?

主要有以下几个:

  • Dispatchers.DEFAULT
  • Dispatchers.IO
  • Dispatchers.MAIN

简单理解调度器视为线程就比较好理解了,比如说我们需要执行长时间的任务,就使用Dispatchers.IO调度器,而需要更改UI,则切换回主线程,如下面代码示例:

btn.setOnClicker{
	//按钮点击触发协程
	val job = CoroutineScope(Dispatchers.Main).launch(Dispatchers.Main){
		val result = withContext(Dispatchers.IO){
			//模拟请求数据,最终得到数据
			"resp"
		}
		//根据result来进行更改UI操作(这里已经在主线程了)
		textview.text = "result"
	}
}

就像之前所说,我们在普通代码中使用launch,就得先创建一个协程作用域CoroutineScope,之后再启动一个协程

CoroutineScope的构造方法需要传一个协程调度器,这里我们就是传了Dispatchers.MAIN,标示此协程作用域默认是在主线程

之后我们也可以通过launch方法来切换不同的线程执行,上面代码中,CoroutineScope和launch都有设置一个调度器

实际上,Dispatchers.MAIN是一个对象,上面代码我们可以省略launch方法里参数,如下代码

btn.setOnClicker{
	//按钮点击触发协程
	val job = CoroutineScope(Dispatchers.Main).launch{
		val result = withContext(Dispatchers.IO){
			//模拟请求数据,最终得到数据
			"resp"
		}
		//根据result来进行更改UI操作(这里已经在主线程了)
		textview.text = "result"
	}
}

如果引用了Javafx的依赖,那么这个Dispatchers.MAINDispatchers.JAVAFX是一个对象,两者可互用

再来说说Dispatchers.IO,实际上这个是类似线程池的东西,创建的协程任务可能会被分配到不同的协程上去执行

协程实际也有有个线程池的,只不过我们使用可以不太关心,当然,如果你需要自己构建一个线程池给协程使用,也有对应方法可以设置,如下方法

// 创建一个包含多个线程的线程池
val customThreadPool = Executors.newFixedThreadPool(4).asCoroutineDispatcher()

runBlocking {
	//启动的设置
	launch(customThreadPool) { 
		 
	}
	//或者
	withContext(customThreadPool) {
		repeat(10) {
			println("Coroutine is running on thread: ${Thread.currentThread().name}")
		}
	}
}

// 关闭线程池
(customThreadPool.executor as? Executors)?.shutdown()

asCoroutineDispatcher()方法,是协程为传统线程池提供的一个扩展方法,可以将线程池转为我们的Dispatcher进行使用(用法方面,和Dispatchers.Main这种对象使用一样)

launch方法

其实关于launch()的使用方法,上面的例子已经介绍的七七八八了,主要是对关于launch()返回的Job对象进行讲解

Job对象有以下常用属性和方法:

  • isActive 当前协程是否已经激活

  • isCompleted 当前协程是否已经完成

  • isCancelled 当前协程是否已经取消

  • cancel() 用于Job的取消,取消协程

  • start() 用于启动一个协程,让其到达Active状态

  • invokeOnCompletion() 当其完成或者异常时会调用

  • join() 阻塞并等候当前协程完成

前3个属性很好理解,这里直接跳过;

注意到有一个start()方法,什么意思呢?因为协程可以设置为懒启动,具体代码如下:

val job = launch(start = CoroutineStart.LAZY) {  }
job.start()

而关于CoroutineStart类,有以下几种选中

  • DEFAULT:默认启动模式,表示协程会立即开始执行。(之前省略不写,就是使用的这个选项)
  • LAZY:懒启动模式,表示协程只有在首次被使用时才会启动执行。
  • ATOMIC:原子启动模式,表示协程会尽快被执行,但可以被取消。
  • UNDISPATCHED:未调度启动模式,表示协程会在当前调用者线程中立即执行,而不进行调度。

至于后2种,目前我没有在具体情景使用,只是做个了解,不扩展进行说明了

invokeOnCompletion方法则是方便我们监听协程完成后的操作,具体示例代码如下:

val job = launch() {  }
job.invokeOnCompletion{
	//相关逻辑
}

这里通过IDE的代码提示,可以看见invokeOnCompletion方法还可以接受2个参数

  • onCancelling job被取消是否触发当前回调,默认为false
  • invokeImmediately 指示指定的代码块是否应立即调用,而不管作业状态如何,默认为true

上面列的几个方法只是常用的,还有些不常用的方法,由于自己不怎么常用,这里就不一一来列出来了

协程并发

async方法

如果说,我们想要实现几个协程并发进行,就可以使用此方法来开启多个协程,如下例子

runBlocking {
	async() {
		//逻辑1
	}
	async() {
		//逻辑2
	}
}

async方法参数和launch方法是一样的,用法方面我这里就不多说什么了,唯一需要注意的是,async方法返回的是一个Deffer对象(虽然它也是继承于Job对象)

如果我们需要等待某个方法的结果的话,可以使用Deffer.await()方法来实现,如下面例子:

runBlocking {
	val deffer = async {
		delay(200)
		5 //这里语法上是kotlin的作用域方法,返回一个int类型,如果不明白的可以自行去了解下
	}
   val result = deffer.await() // result为Int类型,数据为5
}

await()调用后,会使当前协程作用域进行等待,直到协程执行完毕

由于Deffer对象是继承于Job对象,所有Job的相关方法,它也可以用,这里参考上面说到的Job的相关方法即可

最后补充下:

如果我们需要协程并发比较多的话,可以使用一个list来装Deffer对象,最后统一调用await()方法,代码如下:

runBlocking {

	val list = (0..10).map {
		async {
			delay(200)
			5
		}
	}
	
	list.forEach { 
		//每个协程执行结果,做对应逻辑操作
		val result = it.await()
	}
}

不过看到某大佬的文章,提到:协程并发并不是指线程并发,

上面代码实际也可以使用launch方法来实现并发,详见此文Kotlin协程-协程的日常进阶使用 - 掘金

父协程和子协程

还记得上面提到的协程取消方法吗?协程取消,会同时将其有关联的子协程全部依次取消,具体代码:

runBlocking {
	val job1 = launch {
		val deffer = async {
		}

		val job2 = launch {  }
	}
	job1.cancel()
}

如上面示例,job1为父协程,deffer和job2为子协程,当父协程取消,同时deffer和job2也会取消

这里还有一点要说明:

协程的异常是会传递的,比如当一个子协程发生异常时,它会影响它的兄弟协程与它的父协程。而使用了 SupervisorJob() 则意味着,其子协程的异常都将由其自己处理,而不会向外扩散,影响其他协程。

详情文章解释可参考此文Kotlin | 关于协程异常处理,你想知道的都在这里 - 掘金,本文不扩展说明了

一般这样定义一个作用域即可解决问题,代码如下:

private val exceHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
    Log.e("tttt", "协程发生异常", throwable)
}
//调度器Dispatchers.IO根据你自己需要来即可
val gCo = CoroutineScope(SupervisorJob() + Dispatchers.IO + exceHandler)

扩展补充

传统java接口回调如何转协程同步写法

之前一直有个痛点,就是用的是Java库,里面提供的异步操作结果都是通过接口回调的方式来返回数据的,如果我们kotlin中也是去这样写的话,根本就没法体验到协程的优势

kotlin协程,则是提供了一个高级函数suspendCancellableCoroutine{}供我们解决上述问题

这里以一个简单的网络请求为例,有2个接口回调,分别代表请求成功和请求失败

interface RespInterface {
	fun onSuccess(data:String)

	fun onError()
}

Net.post(object :RespInterface{
	override fun onSuccess(data: String) {
	}

	override fun onError() {
	}
})

使用suspendCancellableCoroutine{}改造,代码:

suspend fun myJob() = suspendCancellableCoroutine<String> {
	//下面的it代表CancellableContinuation<String>对象
	Net.post(object :RespInterface{
		override fun onSuccess(data: String) {
			it.resume(data){}
		}

		override fun onError() {
			it.resume(""){}
		}
	})
}

//在协程中调用
runBlocking {
	//result为对应的返回结果
	val result = postNet()
}

suspendCancellableCoroutine{}返回的是CancellableContinuation对象,这里的T类型,就是看你最终调用resume方法返回的对象类型来定义

上面我只是一个简单的例子,如果请求失败,则返回一个空白字符串,到时候逻辑在协程里判断即可

对话框按顺序弹出(Android)

这个同理,也是根据上面的suspendCancellableCoroutine{}方法来实现的,就是有点麻烦,得每个对话框的方法都单独写

下面代码是在Android平台上使用的,使用DialogX库的里的提示框作为示例:

suspend fun showDialog1() = suspendCancellableCoroutine<String> {
	MessageDialog.show("提示1","提示1","确定")
		.setOkButton { dialog, v -> 
			false
		}
		.setDialogLifecycleCallback(object :DialogLifecycleCallback<MessageDialog>(){
			override fun onDismiss(dialog: MessageDialog?) {
				it.resume(""){}
				super.onDismiss(dialog)
			}
		})

}

suspend fun showDialog2() = suspendCancellableCoroutine<String> {
	MessageDialog.show("提示2","提示2","确定")
		.setOkButton { dialog, v ->
			false
		}
		.setDialogLifecycleCallback(object :DialogLifecycleCallback<MessageDialog>(){
			override fun onDismiss(dialog: MessageDialog?) {
				it.resume(""){}
				super.onDismiss(dialog)
			}
		})

}

//使用
lifecycleScope.launch {
	showDialog1()
	showDialog2()
}

如何自定义一个协程作用域

可以直接让我们的类实现 CoroutineScope 接口,但是我们需要指定协程的上下文,如下面代码:

/**
 * 自定义带协程作用域的弹窗
 */
abstract class CoroutineScopeCenterPopup(activity: FragmentActivity) : CenterPopupView(activity), CoroutineScope {

    private lateinit var job: Job

    private val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
        YYLogUtils.e(throwable.message ?: "Unkown Error")
    }

    //此协程作用域的自定义 CoroutineContext
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job + CoroutineName("CenterPopupScope") + exceptionHandler


    override fun onCreate() {
        job = Job()
        super.onCreate()
    }


    override fun onDismiss() {
        job.cancel()  // 关闭弹窗后,结束所有协程任务
        YYLogUtils.w("关闭弹窗后,结束所有协程任务")
        super.onDismiss()
    }
}

上文代码摘抄自Kotlin协程-协程的日常进阶使用 - 掘金,仅供记录方便后来查阅参考

协程常用高阶函数

协程里提供了一些函数使用,上面应该已经介绍的差不多了文章来源地址https://www.toymoban.com/news/detail-841268.html

//创建一个普通的CoroutineScope
coroutineScope {}

//使用SupervisorJob()创建一个CoroutineScope
supervisorScope{}

//执行一个挂起函数,如果超时,抛出TimeoutCancellationException异常!
withTimeout(time Millis: 1000){}

//执行一个挂起函数,如果超时,返回null
withTimeoutorNull(time Millis: 1000) {}

//挂起当前协程,直到协程执行完成,如果传递的context与当前context一致,则该函数不会挂起,相当于阻塞执行
withContext(Dispatchers.I0) {}

//一个方便的可取消的协程作用域
suspendCancellableCoroutine{}

参考

  • kotlinx.coroutines/README.md at master · Kotlin/kotlinx.coroutines
  • 协程基础 · Kotlin 官方文档 中文版
  • Kotlin | 关于协程异常处理,你想知道的都在这里 - 掘金
  • 写给Android工程师的协程指南 - 掘金
  • Kotlin协程解析系列(上):协程调度与挂起 - 知乎
  • 如何通过Kotlin协程, 简化"连续依次弹窗(Dialog队列)"的需求 - 掘金
  • Kotlin协程-CoroutineScope协程作用域 - 掘金
  • Kotlin协程-协程的日常进阶使用 - 掘金

到了这里,关于Kotlin 协程基础使用学习的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【28】Kotlin语法进阶——使用协程编写高效的并发程序

    提示:此文章仅作为本人记录日常学习使用,若有存在错误或者不严谨得地方欢迎指正。 协程是Kotlin语言中很有代表性的一种并发设计模式,用于简化异步执行的代码。 协程和线程有点类似,可以简单地将它理解成一种轻量级的线程 。我们前面学习的线程是属于重量级的,

    2024年02月03日
    浏览(53)
  • Android使用kotlin+协程+room数据库的简单应用

    前言:一般主线程(UI线程)中是不能执行创建数据这些操作的,因为等待时间长。所以协程就是为了解决这个问题出现。 第一步:在模块级的build.gradle中引入   好了前期工作ok,正式编写room吧! 第二步:创建表实体  第三部:编写对应的Dao接口  第四步:创建数据库信息

    2024年02月13日
    浏览(51)
  • 【kotlin 协程】万字协程 一篇完成kotlin 协程进阶

    Kotlin 中的协程提供了一种全新处理并发的方式,可以在 Android 平台上使用它来简化异步执行的代码。协程是从 Kotlin 1.3 版本开始引入,但这一概念在编程世界诞生的黎明之际就有了,最早使用协程的编程语言可以追溯到 1967 年的 Simula 语言。 在过去几年间,协程这个概念发展

    2024年02月07日
    浏览(52)
  • Kotlin 协程一 —— 协程 Coroutine

    1.1.1基本定义 进程 进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。 进程是资源分配的最小单位,在单核CPU中,同一时刻只有一个程序在内存中被CPU调用运行。 线程 基本的

    2024年02月05日
    浏览(50)
  • Kotlin协程-从一到多

    上一篇文章,我介绍了Kotlin协程的创建,使用,协作等内容。本篇将引入更多的使用场景,继续带你走进协程世界。 常用编程语言都会内置对同一类型不同对象的数据集表示,我们通常称之为容器类。不同的容器类适用于不同的使用场景。Kotlin的 Flow 就是在异步计算的需求下

    2024年02月09日
    浏览(56)
  • Kotlin协程学习之-02

    协程的基本使用 GlobalScope.launch 生命周期与进程一致,且无法取消 runBlocking 会阻塞线程,一般在测试阶段可以使用 val coroutineScope = CoroutineScope(context) coroutineScope.launch 通过context参数去管理和控制协程的生命周期 用法 val coroutineScope = CoroutineScope(context) coroutineScope.launch(Dispatche

    2024年01月22日
    浏览(44)
  • Kotlin 协程 - 多路复用 select()

            又叫选择表达式,是一个挂起函数,可以同时等待多个挂起结果,只取用最快恢复的那个值(即多种方式获取数据,哪个更快返回结果就用哪个)。         同时到达 select() 会优先选择先写子表达式,想随机(公平)的话使用 selectUnbiased() 替换 。         能

    2024年02月10日
    浏览(45)
  • Kotlin协程-从理论到实战

    上一篇文章从理论上对Kotlin协程进行了部分说明,本文将在上一篇的基础上,从实战出发,继续协程之旅。 在Kotlin中,要想使用协程,首先需要使用协程创建器创建,但还有个前提——协程作用域( CoroutineScope )。在早期的Kotlin实现中,协程创建器是一等函数,也就是说我们随

    2024年02月09日
    浏览(42)
  • Android Kotlin 协程初探

    维基百科:协程,英文Coroutine [kəru’tin] (可入厅),是计算机程序的一类组件,推广了协作式多任务的子程序,允许执行被挂起与被恢复。 作为Google钦定的Android开发首选语言Kotlin,协程并不是 Kotlin 提出来的新概念,目前有协程概念的编程语言有Lua语言、Python语言、Go语言

    2024年02月08日
    浏览(45)
  • 协程 VS 线程,Kotlin技术精讲

    协程(coroutines)是一种并发设计模式,您可以在Android 平台上使用它来简化异步执行的代码。协程是在版本 1.3 中添加到 Kotlin 的,它基于来自其他语言的既定概念。 在 Android 上,协程有助于管理长时间运行的任务,如果管理不当,这些任务可能会阻塞主线程并导致应用无响应。

    2024年02月09日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包