首页 > 其他分享 >Kotlin协程系列(三)

Kotlin协程系列(三)

时间:2023-12-01 11:45:32浏览次数:38  
标签:协程 函数 launch Kotlin await 调度 async 系列

1.前言

  前面两节,我们运用了kotlin提供的简单协程去实现了一套更易用的复合协程,这些基本上是以官方协程框架为范本进行设计和实现的。虽然我们还没有直接接触kotlin官方协程框架,但对它的绝大多数功能已经了如指掌了。本节,我们来探讨一下官方协程框架的更多功能,并将其运用到实际的生产当中,在这里,我以在Android中使用kotlin官方协程框架为例进行讲述。

2.launch函数启动一个协程

  在Android开发中,我们一般将协程的作用域和Android组件的lifeCycle绑定在一起,这样,当组件销毁的时候,协程的作用域就会取消,协程也就销毁了,这样不会造成内存泄漏。在ViewModel中,我们可以直接使用viewModelScope这个作用域去创建协程,在Activity/Fragment这些拥有生命周期的组件中,我们可以使用lifecycleScope去创建协程,这里我们使用lifecycleScope进行讲述。

  这里我们先给出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
}

  我们先补充一个知识点,协程的启动模式,也就是start参数所设置的,总共有四种启动模式,如下所示:

  1. DEFAULT:创建协程之后,立即开始调度,在调度前如果协程被取消,其将直接进入取消响应状态
  2. ATOMIC:协程创建后,立即开始调度,协程执行到第一个挂起点之前不响应取消
  3. LAZY:只有协程被需要时,包括主动调用start,join,await等函数时才会开始调度,如果调度前被取消协程就会进入异常结束状态
  4. UNDISPATCHED:协程创建之后立即在当前函数的调用栈中执行,直到遇到第一个真正挂起的点

  这里我们要搞清楚立即调度和立即执行的区别,立即调度表示协程的调度器会立即接收调度指令,但具体执行的时机以及在哪个线程上执行还需要根据调度器的情况而定,也就是说立即调度到立即执行前通常会隔一段时间。这里,我们给出一段代码,每隔一段时间打印一个数字:

lifecycleScope.launch{
            for(a in 1..10){
                Log.i("lifecycleScope",a.toString())
                delay(1000)
            }
        }

  这里需要注意的是,如果不指定调度器,那么该协程默认运行在UI线程上,指定调度器可以通过context参数指定,和上一节我们实现的一样,这里不再赘述。

  lauch函数的返回值是Job对象,Job对象常用的属性和函数如下:

  1. isActive:判断Job是否处于活动状态
  2. isCompleted:判断Job是否属于完成状态
  3. isCancelled:判断Job是否被取消
  4. start():开始Job
  5. cancel():取消Job
  6. join():将当前协程挂起,直到该协程完成
  7. cancelAndJoin():前两个函数的结合体

3.async函数启动一个协程

  async和launch函数的不同点在于launch函数启动的协程是没有返回值的,而async函数启动的协程是有返回值的。async函数返回一个Deferred对象,它继承自Job对象,我们可以通过Deferred对象中的await函数获取协程的执行结果,代码如下:

lifecycleScope.launch{
            val deferred=async{
                "计算结果"
            }
            val result=deferred.await()
            Log.i("lifecycleScope",result)
        }

  async函数和launch函数的共同点是他们不会等待协程执行结束,会立马往下执行,测试如下:

lifecycleScope.launch {
            lastTime = System.currentTimeMillis()
            async {
                delay(1000)
            }
            async {
                delay(1000)
            }
            Log.i("耗时", (System.currentTimeMillis() - lastTime).toString())
        }

  打印的结果是1ms,并不是2000毫秒,也就是说多个async函数是并行执行的,当然,这里换成launch结果也是一样的。当然,如果你在后面加一个await函数,那么结果就是2000ms左右了,也就是这样:

lifecycleScope.launch {
            lastTime = System.currentTimeMillis()
            async {
                delay(1000)
            }.await()
            async {
                delay(1000)
            }.await()
            Log.i("耗时", (System.currentTimeMillis() - lastTime).toString())
        }

  使用launch加上join函数的结果也是一样的。如果我再换一种写法:

lifecycleScope.launch {
            lastTime = System.currentTimeMillis()
            val job1=launch {
                delay(1000)
            }
            val job2=launch {
                delay(1000)
            }
            job1.join()
            job2.join()
            Log.i("耗时", (System.currentTimeMillis() - lastTime).toString())
        }

  这里的执行结果是1000毫秒左右,可以自行尝试,换成async函数加await也是一样的。

  通过上面的测试,我们可以得出结论,launch函数和async函数启动的协程是并行执行的,并且启动协程之后会立马往下执行,不会等待协程完成,除非调用join或await函数。launch函数和async函数的唯一区别就是async函数启动的协程有返回值,如果不需要获取协程的执行结果,那么没必要用async函数。

4.withContext函数的作用

  官方框架中还为我们提供了一个好用的api,withContext(),它的定义如下:

public suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T 

  withContext会将参数中的lambda表达式调度到由context指定的调度器上运行,并且它会返回协程体当中的返回值,它的作用几乎和async{}.await()等价,但和async{}.await()相比,它的内存开销更低,因此对于使用async后立即要调用await的情况,应当优先使用withContext函数。而且有了withContext之后,在Android开发的时候,就可以不再使用Handler了,我们可以在需要进行耗时操作(网络请求,数据库读写,文件读写)时,使用withContext切换到IO线程上,在得到想要的结果后要更新UI时又可以切换到UI线程上,非常的方便。测试如下:

lifecycleScope.launch(Dispatchers.IO) {
            delay(1000)
            val result="JJLin"
           withContext(Dispatchers.Main){
               tv_display.text=result
           }
        }

  这段代码模拟了在IO线程上进行耗时操作,可以是数据库访问,网络请求之类的;拿到结果后,用withContext切换到主线程,进行UI的更新。

5.协程的超时取消

  kotlin官方协程框架为我们提供了一个withTimeout()函数用于执行超时取消设置,这个api的定义如下:

public suspend fun <T> withTimeout(timeMillis: Long, block: suspend CoroutineScope.() -> T): T

  这个函数可以设置一个超时时间,超过这个时间后就会通过抛出异常来取消这个协程,如果不想抛出异常,可以使用withTimeoutOrNull,这个函数在超时之后会返回null,而不会抛出异常。

 

标签:协程,函数,launch,Kotlin,await,调度,async,系列
From: https://www.cnblogs.com/luqman/p/coroutine3.html

相关文章

  • Unity DOTS系列之Filter Baking Output与Prefab In Baking核心分析
    最近DOTS发布了正式的版本,我们来分享一下DOTS里面Baking核心机制,方便大家上手学习掌握UnityDOTS开发。今天给大家分享的Baking机制中的FilterBakingOutput与PrefabInBaking。对啦!这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础小白,也有一些正在从事游戏开发......
  • Unity 最新DOTS系列之《Baking与Baker的详解》
    UnityDOTSBaking与Baker详解UnityDOTSBaking与Baker详解最近DOTS终于发布了正式的版本,我们来分享一下DOTS里面Baking与Baker的关键概念,方便大家上手学习掌握UnityDOTS开发。UnityDOTS开发模式,为了让大家在”创作”游戏的时候使用原来组件方式来编辑游戏场景与资源,同......
  • [Unraid 系列 v6.10+] 8 删除 已挂载的zfs格式硬盘 中无用共享文件夹
    说明在使用zfs文件格式并格式化硬盘后,无法通过rm命令删除已共享的文件夹(哪怕已经删除共享)。示例:root@unraid:/mnt/disk2#rm-rappdata/rm:cannotremove'appdata/':Deviceorresourcebusy解决通过zfsdestroy-r命令删除文件夹。示例:root@unraid:/mnt/di......
  • 数据库系列:MySQL InnoDB锁机制介绍
    数据库系列:MySQL慢查询分析和性能优化数据库系列:MySQL索引优化总结(综合版)数据库系列:高并发下的数据字段变更数据库系列:覆盖索引和规避回表数据库系列:数据库高可用及无损扩容数据库系列:使用高区分度索引列提升性能数据库系列:前缀索引和索引长度的取舍数据库系列:MySQL引擎My......
  • go的GPM - 协程的本质
    协程与线程线程在创建、切换、销毁时候,需要消耗CPU的资源。协程就是将一段程序的运行状态打包,可以在线程之间调度。减少CPU在操作线程的消耗进程用分配内存空间线程用来分配CPU时间协程用来精细利用线程协程的本质是一段包含了运行状态的程序后面介绍后,会对这个概念更好......
  • PVE 系列之二:安装基于 LXC 的容器
    基于LXC的容器CT模板换源注意版本是否适配cp/usr/share/perl5/PVE/APLInfo.pm/usr/share/perl5/PVE/APLInfo.pm_backsed-i's|http://download.proxmox.com|https://mirrors.tuna.tsinghua.edu.cn/proxmox|g'/usr/share/perl5/PVE/APLInfo.pm重启服务。systemctlr......
  • Java系列---【时间格式合法性校验】
    #不能用LocalDate.parse(),解析不了20230231,默认会解析成20230228,并且不抛异常,用Strict模式,虽然会抛异常但无法解析20230201,推荐下面的publicstaticbooleanisValidDateFormat(Stringdate,Stringformat){if(date.length!=format.length){returnfalse;}......
  • SIEM系列|一文读懂 Linux 日志安全分析之文件监控
    摘自:https://zhuanlan.zhihu.com/p/259808863背景介绍在Linux操作系统中,所有内容都是以文件的形式保存和管理的,包括普通文件、目录、网络通信资源等都是文件,即“一切皆文件”。基于这种机制,针对Linux系统层的攻击方式,本质上往往是通过各种方式,对某些敏感文件进行篡改,使入侵......
  • Rust Tauri系列: 项目创建
    创建Rust-Tauri##创建rustTauri项目pnpmcreatetauri-app->项目名称test-app->选择TypeScript/JavaScript(pnpm,yarn,npm,bun)->选择包管理工具(熟悉那个就用那个)->选择vue(熟悉那个就用那个)->选择TypeScript或者js#运行启动cdtest-apppnpmins......
  • kotlin orm kotysa笔记
    依赖implementation("org.ufoss.kotysa:kotysa-spring-jdbc:3.2.1")implementation("org.springframework.data:spring-data-jdbc")implementation("com.alibaba:druid:1.2.20")runtimeOnly("org.postgresql:postgresql")yaml配置......