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,而不会抛出异常。

Kotlin协程系列(三)的更多相关文章

  1. Kotlin协程解析系列(上):协程调度与挂起

    vivo 互联网客户端团队- Ruan Wen 本文是Kotlin协程解析系列文章的开篇,主要介绍Kotlin协程的创建.协程调度与协程挂起相关的内容 一.协程引入 Kotlin 中引入 Corout ...

  2. 深入理解协程(三):async/await实现异步协程

    原创不易,转载请联系作者 深入理解协程分为三部分进行讲解: 协程的引入 yield from实现异步协程 async/await实现异步协程 本篇为深入理解协程系列文章的最后一篇. 从本篇你将了解到: ...

  3. Kotlin 协程一 —— 全面了解 Kotlin 协程

    一.协程的一些前置知识 1.1 进程和线程 1.1.1基本定义 1.1.2为什么要有线程 1.1.3 进程与线程的区别 1.2 协作式与抢占式 1.2.1 协作式 1.2.2 抢占式 1.3 协程 二 ...

  4. {python之协程}一 引子 二 协程介绍 三 Greenlet 四 Gevent介绍 五 Gevent之同步与异步 六 Gevent之应用举例一 七 Gevent之应用举例二

    python之协程 阅读目录 一 引子 二 协程介绍 三 Greenlet 四 Gevent介绍 五 Gevent之同步与异步 六 Gevent之应用举例一 七 Gevent之应用举例二 一 引子 本 ...

  5. Kotlin协程第一个示例剖析及Kotlin线程使用技巧

    Kotlin协程第一个示例剖析: 上一次https://www.cnblogs.com/webor2006/p/11712521.html已经对Kotlin中的协程有了理论化的了解了,这次则用代码来直 ...

  6. Retrofit使用Kotlin协程发送请求

    Retrofit2.6开始增加了对Kotlin协程的支持,可以通过suspend函数进行异步调用.本文简单介绍一下Retrofit中协程的使用 导入依赖 app的build文件中加入: impleme ...

  7. Kotlin协程基础

    开发环境 IntelliJ IDEA 2021.2.2 (Community Edition) Kotlin: 212-1.5.10-release-IJ5284.40 我们已经通过第一个例子学会了启 ...

  8. Android Kotlin协程入门

    Android官方推荐使用协程来处理异步问题.以下是协程的特点: 轻量:单个线程上可运行多个协程.协程支持挂起,不会使正在运行协程的线程阻塞.挂起比阻塞节省内存,且支持多个并行操作. 内存泄漏更少:使 ...

  9. rxjava回调地狱-kotlin协程来帮忙

    本文探讨的是在tomcat服务端接口编程中, 异步servlet场景下( 参考我另外一个文章),用rxjava来改造接口为全流程异步方式 好处不用说 tomcat的worker线程利用率大幅提高,接口 ...

  10. Kotlin协程通信机制: Channel

    Coroutines Channels Java中的多线程通信, 总会涉及到共享状态(shared mutable state)的读写, 有同步, 死锁等问题要处理. 协程中的Channel用于协程间 ...

随机推荐

  1. VMware中的三种网络模式

    1.桥接模式网络 通过桥接模式网络连接,虚拟机中的虚拟网络适配器可连接到主机中的物理网络适配器.虚拟机可通过主机网络适配器连接到主机系统所用的 LAN.桥接模式网络连接支持有线和无线主机网络适配器. ...

  2. 【go笔记】简单的http服务

    前言 Go语言通过内置的标准库net/http可以非常方便地实现web服务.不借助任何框架,单凭标准库,50行代码内即可实现简单的web服务. http的ListenAndServe()函数原型: f ...

  3. Oracle表的导出、导入

    有些情况下,需要单独导出某些表,用或者分析数据. 下面记录Oracle表的导出导入方法 1. 表的导出 ./exp $username/$passwd@$ORACLE_SID file=/$file_ ...

  4. vue中添加音频和视频

    视频播放功能 1. 安装vue-video-player npm install vue-video-player --save 或 yarn add vue-video-player --save ...

  5. Mysql高阶自定义排序

    Mysql高阶自定义排序 嗨,大家好,我是远码,隔三岔五给大家分享一点工作的技术总结,花费的时间不多,几分钟就行,谢谢! Mysql对我们码农来说是在熟悉不过的日常了,就不在介绍它的基础用法了,今天我 ...

  6. 《SQL与数据库基础》18. MySQL管理

    目录 MySQL管理 系统数据库 常用工具 mysql mysqladmin mysqlbinlog mysqlshow mysqldump mysqlimport source 本文以 MySQL ...

  7. API接口的设计思路

    ​ API接口设计是软件开发中非常重要的一环,良好的设计规范能够提高开发效率.减少问题和错误,并增强系统的可维护性和可扩展性.本文从程序员的视角,讨论一些常见的API接口设计规范. 一.遵循RESTf ...

  8. SpringBoot获取树状结构数据-SQL处理

    前言 在开发中,层级数据(树状结构)的获取往往可能是我们一大难点,我现在将自己获取的树状结构数据方法总结如下,希望能给有需要的小伙伴有所帮助! 一.测试数据准备 /* Navicat Premium ...

  9. 分拣平台API安全治理实战 | 京东物流技术团队

    导读 本文主要基于京东物流的分拣业务平台在生产环境遇到的一些安全类问题,进行定位并采取合适的解决方案进行安全治理,引出对行业内不同业务领域.不同类型系统的安全治理方案的探究,最后笔者也基于自己在金融领 ...

  10. 前端三件套系例之CSS——CSS是什么、CSS3语法、css代码书写位置(引入方式)、css选择器

    文章目录 1.CSS是什么 2.CSS3语法 2.1 CSS实例 2.2 CSS注释 3.css代码书写位置(引入方式) 3-1 行间式 3-2 内联式 3-3 外联式 总结 3 css选择器 1.基 ...