kotlin之协程的理解与使用详解
作者:何33512336 发布时间:2023-10-21 15:55:57
前言
为什么在kotlin要使用协程呢,这好比去了重庆不吃火锅一样的道理。协程的概念并不陌生,在python也有提及。任何事务的作用大多是对于所依赖的环境相应而生的,协程对于kotlin这门语言也不例外。协程的优点,总的来说有如下几点:轻量级,占用更少的系统资源; 更高的执行效率; 挂起函数较于实现Runnable或Callable接口更加方便可控; kotlin.coroutine 核心库的支持,让编写异步代码更加简单。当然在一些不适应它的用法下以上优势也会成为劣势。
1.协程定义
协程定义:kotlin官方基于JVM的线程实现的一个并发任务处理框架,封装的线程api
使用方便,不使用回调实现线程切换,使用同步方式写出异步代码
所有的耗时任务保证一定放在后台执行挂起
函数执行完毕之后,协程会把它切换到原先的线程的线程。
2.协程的基本用法
常规函数中一般都有:call and return,协程在此之外添加了suspend和resume.
suspend 用于暂停执行的当前协程,并保存所有的局部变量
resume 用于已暂停的协程中暂停出恢复
supend(挂起函数)是什么,有什么意义
suspend,对协程的挂起并没有实际作用,其实只是一个提醒,函数创建者对函数的调用者的提醒,提醒调用者我是需要耗时操作,需要用挂起的方式,在协程中使用.
需要注意的是挂起函数只能在挂起函数或者协程作用域中使用,为什么挂起函数需要在协程作用域中使用?因为普通函数没有suspend和resume这两个特性,所以必须要在协程的作用中使用。
意义:
语法层面:作为一个标记和提醒。通过报错来提醒调用者和编译器,这是一个耗时函数,需要放在后台执行。
编译器层面:辅助 Kotlin 编译器来把代码转换成 JVM 的字节码。
怎么自定义suspend函数?
什么时候定义?
需要耗时操作的时候,需要定义,例如io耗时操作(请求网络);获取数据库数据;一些等待一会需要的操作;列表排除,json解析等;
怎么写suspend函数,给函数前加上suspend 关键字,把内容用withContext包起来
suspend fun testSuspendfun(){
withContext(Dispatchers.IO){
}
}
协程如何确保主线程安全
Dispatchers.Main 调用程序在Android的主线程中
Dispatchers.IO 适合主线程之外的执行磁盘或者网络io操作,例如文件的读取与写入,任何的网络请求
Dispatcher.Default 适合主线程之外的,cpu的操作,例如json数据的解析,以及列表的排序,
协程的挂起本质:本质就是切线程,完成之后只不过可以自动切回来
协程挂起就是切个线程,在挂起函数执行完毕之后,协程会自动的重新切回它原先的线程,也就是稍后会被切回来的线程切换。切回来就是resume,恢复功能是协程,所以suspend函数需要在另一个suspend函数或者协程中调用。「非阻塞式挂起」阻塞的方式写出了非阻塞的方式。
3.协程的创建以及取消
//创建一个协程
Val scope = CoroutineScope(Dispatchers.Main+Job())
通过Job获取协程的生命周期
scope.launch{
}
其他耗时请求,例如从数据库中获取数据
scope.async {
}
在KTX库为某些生命周期提供自己的CoroutineScope,例如ViewModel中viewModelScope,Lifecycle有lifecycleScope
协程的启动,launch 启动新协程而不将结果返回给调用方
//创建之后,不管后续
launch(){
}
async 启动一个新协程,并通过deferred的await方法暂停函数
//返回deferred 对象
val deferred async{
}
deferred.await()
协程的结构化并发,取消协程
协程的结构化并发,可以让协程非常便于管理。例如在关闭activity中要取消协程。如果是在线程中,取消所有的线程比较复杂。
取消父协程以及父里面的子协程
val scope = CoroutineScope(Dispatchers.Main+ Job())
scope.launch {
val job = launch {
val job1 = launch {
}
}
job.cancel()
}
scope.cancel()
取消子协程某一个,每一个协程都会返回一个job对象,通过调用job的cancle,可以去取消单个的协程的。
val scope = CoroutineScope(Dispatchers.Main+ Job())
scope.launch {
val job = launch {
val job1 = launch {
}
}
job.cancel()
}
scope.cancel()
4.协程中异常处理
在协程内部中捕获异常
val scope = CoroutineScope(Dispatchers.Main+ Job())
scope.launch {
try {
}catch (e:Exception){
}
}
5.协程的优势
在程序运行过程中某些操作(像是:网络IO、文件IO、CPU或GUP计算密集型工作等等)可能会耗费大量的时间,在单线程的环境下可能会造成线程的阻塞,在他们完成之前没办去做其它事情。使用传统方法的话,我们可能会选择使用多线程来解决这个问题,将这些耗时操作放置到新的线程中去执行,使主线程能够正常的运行。那么本文标题所提到的协程是怎么一回事呢?
协程可以看作是一个轻量级的线程,他不是由操作系统或是虚拟机来实现的,而是通过编译器。这意味着相对于线程,协程的开销更小。大家可以从下面的这个例子中感受一下。
下面是一段Kotlin使用协程的代码,创建了100万个协程 (官方的例子是使用的100K,不过运行时间太短,不好截内存的使用情况)。
fun main(args: Array)= runBlocking { val jobs= List(1_000_000){
launch(CommonPool){
delay(10L)
println(it)
}
}
jobs.forEach { it.join() }
}
内存使用情况
运行耗时:
然后是使用线程来进行实现的代码:
fun main(args: Array) { val threadList=List(1_000_000){
Thread{
Thread.sleep(10L)
println(it)
}
}
threadList.forEach { it.start();it.join() }
}
内存使用情况:
运行耗时:10分钟以上。
使用线程的代码,占用的内存几乎是使用协程的两倍。而且从运行时间上看使用协程实现的程序话费的时间要远远低于线程的实现方式。单从这两点来看,协程拥有更高的执行效率,占用更少的系统资源。那么Kotlin中的协程是通过什么来实现异步操作的呢?它使用的是一种叫做 挂起 的机制。协程的挂起几乎是没有损耗的,换种说法,就是不需要选择额外的上下文或是操作系统调用。 另外一点, 挂起能很大程度上被用户库给控制:我们可以决定在挂起状态下具体做些什么,并且围绕着需求进行优化/日志/拦截等操作。
协程不能随随便便就被挂起,只能在一个称为挂起点的地方,在这里会去调用特别标记的函数。这样的函数被称作 挂起函数,因为你调用他们会挂起一个协程(如果允许这次调用的话,库可以直接进行处理而不需要挂起)。 挂起函数的声明需要添加suspend修饰符。例如:
suspend fun doSomething(foo: Foo): Bar {
...
}
挂起函数就像平常使用的函数一样,可以有参数和返回值,但是他们只能被协程或是其它挂起函数调用。事实上,要想启动一个协程,至少得有一个挂起函数,并且一般是匿名的(也就是一个挂起lambda表达式)。
线程往往是没有返回值(实现Runnable接口),尽管可以通过实现Callable接口来获得带返回值的线程。但这与协程在语法层面上的支持,在使用的便捷性上还是有不少差距的。
协程是通过编译技术实现的 (不需要虚拟机或操作系统的特别支持),这一点在开头也提到了。挂起操作通过代码变换实现。基本上,每一个挂起函数(可能会进行优化,但我们在着不想讨论这点)都被转换成一个状态机,那些状态与挂起调用相对应。在一个挂起准备好之前,下一状态与相关局部变量等一起存储在编译器生成的类的字段中。在恢复该协程时,恢复局部变量并且状态机从刚好挂起之后的状态进行。挂起的协程可以作为保持其挂起状态与局部变量的对象来存储和传递。
许多其它语言实现的异步机制也能制作成库,在Kotlin的协程中使用。包括:C#和ECMAScript写的 async/await , channels Go语言写的 select ;C#和Python写的 generators/yield 。
来源:https://blog.csdn.net/baidu_33512336/article/details/109016328


猜你喜欢
- 项目中需要实现一个状态显示的悬浮框,要求可以设置两种模式:拖动模式和不可拖动模式。实现效果图如下:实现步骤:1.首先要设置该悬浮框的基本属性
- Java 实现FTP服务实例详解1、FTP简介 FTP
- 本文讲述了Java开发人员需知的十大戒律。分享给大家供大家参考,具体如下:作为一个Java开发人员提高自己代码的质量,可维护性,是个恒久不变
- 实现效果:奔溃的线程侠:(单线程)主线程正在处理刷新图片的请求时,无法再接受其他请求,从而陷入阻塞的死循环状态。绘制图片import jav
- 一、需求一般项目中都需要作异常处理,基于系统架构的设计考虑,使用统一的异常处理方法。系统中异常类型有哪些?包括预期可能发生的异常、运行时异常
- 概念代理:为控制A对象,而创建出新B对象,由B对象代替执行A对象所有操作,称之为代理。一个代理体系建立涉及到3个参与角色:真实对象(A),代
- 今天给大家介绍一下如何实现一款简约时尚的安卓登陆界面。大家先看一下效果图当用户输入时动态出现删除按钮 现在先罗列一下技术点:1.如何使用圆角
- 本文实例为大家分享了使用ContentProvider实现查看系统短信功能的具体代码,供大家参考,具体内容如下activity_main.x
- 项目结构:pom.xml文件: <parent>
- for循环for循环语句是支持迭代的一种通用结构,是最有效,最灵活的循环结构。for循环执行的次数是在执行前就确定的。语法格式如下:for(
- 本文实例为大家分享了java代码统计小程序,供大家参考,具体内容如下可以测试每周你的工作量package rexExp;import jav
- 本文实例为大家分享了JFinal使用ajaxfileupload实现图片上传预览的具体代码,供大家参考,具体内容如下1.前端jsp页面核心代
- 这篇文章主要介绍了Spring MVC处理方法返回值过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需
- 这篇文章主要介绍了java操作elasticsearch的案例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价
- 本文介绍了Maven+Tomcat8 实现自动化部署的方法,分享给大家,具体如下:1.配置tomcat-users.xml首先在Tomcat
- 今天要介绍一个概念,对象的克隆。本篇有一定难度,请先做好心理准备。看不懂的话可以多看两遍,还是不懂的话,可以在下方留言,我会看情况进行修改和
- 高并发下restTemplate的错误分析1. 问题现象和分析org.apache.http.conn.ConnectionPoolTime
- 一、获取系统当前时间long startTime = System.currentTimeMillis(); //获取开始时间doSomet
- 前言借用《Effactive Java》这本书中的话,float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点
- 本文实例为大家分享了unity实现场景跳转的具体代码,供大家参考,具体内容如下话不多说直接开始操作步骤。1.打开我们的unity创建一个工程