当前位置: 首页 > news >正文

Android --- Kotlin学习之路:协程的使用,什么是协程,为什么要用协程?(学习笔记)

Kotlin 协程(coroutine)学习
以下干货满满,掌握以下内容一定会对你在项目开发中有所帮助,记得收藏!!!

文章目录

    • 什么是协程,为什么要用协程?
    • 挂起函数挂起的是什么?
    • 挂起函数中的suspend 关键字的作用是什么?
    • 如果你想在协程中执行串行的代码,而不是并行的,怎么处理?
    • 怎么样写一个自定义挂起函数?
    • 并行协程的启动和交互
    • 什么是耗时操作?
    • 挂起和阻塞的区别,什么叫挂起,什么叫阻塞?
    • 协程所使用的调度器
    • 定义协程必须指定其 CoroutineScope,常用的相关API有:
    • 协程的启动
    • 协程的启动模式(指定协程启动之后的一些行为)
    • 协程的作用域构建器
    • Job对象
    • CPU密集型任务的取消
    • 协程取消的副作用
    • 超时任务怎么处理

什么是协程,为什么要用协程?

协程是基于线程,它是轻量级的线程
java用的是线程,kotlin用的是协程,协程是个并发管理工具,定位跟线程其实是一样的。
其实就是把java的线程包了一层。之前我们做网络请求的时候,想把网络请求的结果响应在主线程中,应该怎么写?
一般情况下我们会在主线程去开启一个子线程发起网络请求,然后将请求的结果在主线程显示
比如Retrofit,会帮我们实现自动在子线程去发起网络请求,不用我们自己new Thread,然后
会把结果通过onResponse的形式传递给我们,这个时候Retrofit也帮我们切到了主线程,而协程并不需要写回调了。
另外,比如我们想要先开启两个并行协程,然后需要最终合并到主线程,协程是实现是最简单的。

interface ApiService {@GET(value = "posts/1")fun queryUser(@Query(value = "userId", encoded = true) userId: String) : Call<UserResponse>@GET(value = "user")suspend fun getUser(@Query(value = "userid", encoded = true) username:String) :UserResponse
}

老的写法:

// Retrofit 请求
val create = Retrofit.create(ApiService::class.java)
create.queryUser("1").enqueue(object : Callback<UserResponse> {override fun onResponse(call: Call<UserResponse>, response: Response<UserResponse>) {if (response.isSuccessful) {val body = response.body()body?.let {Log.i("Retrofit", "get success : $it")}}}override fun onFailure(call: Call<UserResponse>, t: Throwable) {Log.e("Retrofit", "get onFailure : ${t.message}")}
})

协程的写法:

CoroutineScope(Dispatchers.Main).launch { // 主线程开启一个协程val create = Retrofit.create(ApiService::class.java)val user = create.getUser("zhangqi") // 这里调用的是一个挂起函数,挂起函数会在后台执行,原理是协程的Dispatchers.Main自己有线程池// 这一行可以做页面刷新,这行操作自动在主线程中执行,原理是,挂起函数会调用handler.post切换到主线程的
}

挂起函数挂起的是什么?

挂起的是协程,挂起之后,协程就会脱离当前线程,等待挂起函数执行完成之后再切回来

挂起函数中的suspend 关键字的作用是什么?

提醒函数调用者,我是一个耗时函数,需要放在后台运行,请在协程中调用我
suspend 只是提醒

如果你想在协程中执行串行的代码,而不是并行的,怎么处理?

需要使用withContext

CoroutineScope(Dispatchers.Main).launch {launch {println("--1-- ${Thread.currentThread().name}")}println("--2-- ${Thread.currentThread().name}")
}
上面的代码输出结果:
--2-- main
--1-- main

那如果我们想要按照顺序输出,就需要这样写

CoroutineScope(Dispatchers.Main).launch {withContext(Dispatchers.IO) {println("--1-- ${Thread.currentThread().name}")}println("--2-- ${Thread.currentThread().name}")
}
输出结果:
--1-- DefaultDispatcher-worker-1
--2-- main

因为withContext将线程切到了IO线程,所以后面的操作要等待withContext里面的内容执行完才能执行

怎么样写一个自定义挂起函数?

本质上没有自定义挂起函数,就是使用suspend关键字,
然后用挂起函数withContext(){}将你的耗时内容包裹起来

并行协程的启动和交互

lifecycleScope.launch {// deferred1 和 deferred2 并行val deferred1 = async {println("Test1 ${Thread.currentThread().name}") }val deferred2 = async {println("Test2 ${Thread.currentThread().name}") }showContributors(deferred1.await() + deferred2.await()) // 将上面两个协程的结果合并
}

什么是耗时操作?

文件读写,网络请求,等待操作(delay(10))等等,都是耗时操作

挂起和阻塞的区别,什么叫挂起,什么叫阻塞?

挂起就相当于delay(12000)
阻塞就相当于Thread.sleep(12000)
挂起之后你可以干其他事,但是阻塞之后你就要等,不能干其他事
什么叫挂起:当前协程不再占用它正在工作的这个线程

协程所使用的调度器

Dispatcher.Main 主线程,底层还是用原生handler.post()方法切到主线程的
Dispatcher.IO 非主线程 ,底层有自己的Executor管理
Dispacther.Default 非主线程,底层有自己的Executor管理
Dispacther.Unconfiend 基本不会用到

DEFAULT和IO的区别:
DEFAULT 的线程数量是CPU的核心数相等,面向计算密集型任务
IO 是固定的64个线程的线程池,面向I/O密集型型任务(读写文件,网络访问),CPU基本不工作,I/O的磁盘在做的

协程能够通过挂起函数实现切换线程之后切回来

定义协程必须指定其 CoroutineScope,常用的相关API有:

GlobalScope,生命周期是process级别的,顶级的,即使Activity或Fragment已经被销毁了,协程仍然在执行
MainScope, 在Activity中使用,可以在onDestory()中取消协程
viewModelScope,只能在ViewModel中使用,绑定ViewModel的生命周期
lifecycleScope,只能在Activity,Fragment中使用,会绑定Activity和Fragment的生命周期 ,会自动在onDestory 调用自己的cancel()方法取消

协程的启动

通过launch和async构建器启动协程
我们可以用async先启动一个协程,然后在别的协程中通过.await()获取它的结果

lifecycleScope.launch {// deferred1 和 deferred2 并行val deferred1 = async {println("Test1 ${Thread.currentThread().name}") }val deferred2 = async {println("Test2 ${Thread.currentThread().name}") }showContributors(deferred1.await() + deferred2.await()) // 将上面两个协程的结果合并
}

通过join和await等待作业
launch和async的区别,launch 返回的是一个Job对象,async返回的是一个Defferd对象,Defferd本质也是一个Job
launch常用于不需要返回结果的并发任务,async则常用在需要返回结果的并发任务中

val scope = CoroutineScope(Dispacther.Default)
scope.launch {}

协程的启动模式(指定协程启动之后的一些行为)

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

val job = async(context = Dispacther.IO, start = CoroutineStart.UNDESPATCHED){println("thread"&{Thread.currentThread().name})
} 
//这段代码输出的是主线程

协程的作用域构建器

coroutineScope 和 runBlocking
runBlocking 是常规函数,会阻塞线程,coroutineScope是挂起函数。
coroutineScope 一个协程失败了,所有其他其他兄弟协程也会被取消
supervisorScope 一个协程失败了,不会影响其他协程的执行
coroutineScope 和 runBlocking会等待所有的字协程执行完毕,这个作用域才会结束

Job对象

负责管理协程的生命周期
新创建(New)活跃(Active)完成中(Completing)已完成(Completed)取消中(Cancelling)已取消(Cancelled)

CPU密集型任务的取消

要通过isActive ensureActive() yield 取消

协程取消的副作用

如果在取消协程之后做了一些释放资源的操作,那么就坏菜了。我们可以通过try{}finally{在这里面释放资源,这里的东西一定执行}
还可以用use函数,use函数内部会自己close()资源

超时任务怎么处理

val result = withTimeoutOrNull(1300) {repeat(1000) { i ->println("job: I'm sleeping &i")delay(500L)}"Done"
}?: "Jack"

相关文章:

  • Docker 和 k8s 之间是什么关系?
  • 通义千问AI模型对接飞书机器人-模型配置(2-1)
  • HarmonyOS ArkUi @CustomDialog 和promptAction.openCustomDialog踩坑以及如何选择
  • Python--PyMySQL 库基础操作笔记
  • LeetCode热题100(JavaScript)
  • HTTP状态码(HTTP Status Code)讲解
  • k8s上部署openvpn
  • IP地址:由电脑还是网线决定?
  • 【产品评测】海康威视(HIKVISION)NAS网络存储——简单评测
  • PostgreSQL安装/卸载(CentOS、Windows)
  • docker 部署wechatbot-webhook 并获取接口实现微信群图片自动保存到chevereto图库等
  • 计算机网络入门 -- 常用网络协议
  • el-menu弹出菜单样式不生效
  • 十一、数组(1)
  • 7.SpringBoot整合Neo4j
  • es6要点
  • laravel 用artisan创建自己的模板
  • Median of Two Sorted Arrays
  • RxJS 实现摩斯密码(Morse) 【内附脑图】
  • 初识 beanstalkd
  • 浮动相关
  • 给github项目添加CI badge
  • 聊聊hikari连接池的leakDetectionThreshold
  • 批量截取pdf文件
  • 融云开发漫谈:你是否了解Go语言并发编程的第一要义?
  • 我从编程教室毕业
  • 小而合理的前端理论:rscss和rsjs
  • mysql面试题分组并合并列
  • 阿里云API、SDK和CLI应用实践方案
  • ​如何在iOS手机上查看应用日志
  • # centos7下FFmpeg环境部署记录
  • # 数据结构
  • #传输# #传输数据判断#
  • (09)Hive——CTE 公共表达式
  • (react踩过的坑)antd 如何同时获取一个select 的value和 label值
  • (第27天)Oracle 数据泵转换分区表
  • (附源码)spring boot公选课在线选课系统 毕业设计 142011
  • (附源码)springboot建达集团公司平台 毕业设计 141538
  • (附源码)springboot美食分享系统 毕业设计 612231
  • (附源码)springboot人体健康检测微信小程序 毕业设计 012142
  • (附源码)ssm考生评分系统 毕业设计 071114
  • (免费领源码)python+django+mysql线上兼职平台系统83320-计算机毕业设计项目选题推荐
  • (三)docker:Dockerfile构建容器运行jar包
  • (三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练
  • (十六)视图变换 正交投影 透视投影
  • (实战篇)如何缓存数据
  • (一)eclipse Dynamic web project 工程目录以及文件路径问题
  • (一)pytest自动化测试框架之生成测试报告(mac系统)
  • (原創) 如何將struct塞進vector? (C/C++) (STL)
  • (转)大道至简,职场上做人做事做管理
  • .equal()和==的区别 怎样判断字符串为空问题: Illegal invoke-super to void nio.file.AccessDeniedException
  • .net core 6 集成 elasticsearch 并 使用分词器
  • .NET Core IdentityServer4实战-开篇介绍与规划
  • .Net Core 中间件与过滤器
  • .NET MVC第三章、三种传值方式