走进协程的世界
记录学习,持续更新。
相关知识,在代码注释中 进行了具体说明和个人见解。学习阶段,可能不太准确,仅供参考~
- 协程基础了解
- 协程作用域构建器
- 协程结构化并发
- 协程的生命周期
- 协程的取消
- 协程上下文
- 协程的异常捕捉
package com.corroutine
import android.os.Bundle
import android.os.Environment
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*
import java.io.BufferedReader
import java.io.FileReader
import kotlin.coroutines.*
import kotlin.system.measureTimeMillis
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
corroutine()
}
/**
*协程让异步逻辑同步化,杜绝回调地狱
*
* 协程实现分为两个层次:
* 基础设施层,标准库的协程API kolin包下面的
* 业务框架层,协程的上层框架支持 kotlinx包下面的 eg. GlobalScope delay(100)
*/
fun corroutine() {
//GlobalScope:顶级协程
//launch:协程构建器
//Dispatchers:协程调度器 Dispatchers.Main、 Dispatchers.Default(非主线程) 、Dispatchers.IO(非主线程) 不指定调度器的时候 默认是Dispatchers.Default
GlobalScope.launch(Dispatchers.Main) {
/**
*协程内部调用的必须是挂起函数 suspend
*
* 理解挂起和阻塞的区别
*/
delay(100)
val a = getUser()
Log.d("fct", a)
}
}
private suspend fun getUser() = withContext(Dispatchers.IO) {
"1111"
}
/**
*使用基础设施层创建协程 原生API
* CoroutineContext:协程上下文
*/
fun 原生api创建协程() {
//创建协程
val continuation = suspend {
//协程体
5
}.createCoroutine(object : Continuation<Int> {
override val context: CoroutineContext
get() = EmptyCoroutineContext
override fun resumeWith(result: Result<Int>) {
println(result)
}
})
//启动协程
continuation.resume(Unit)
}
/**
* 协程结构化并发:就是有个管理员可以统一管理这些协程,管理员可以取消协程、追踪协程、以及协程失败时的异常监听处理。
* 这个管理员就是协程作用域CoroutineScope
* 带有默认协程作用域的有:
* GlobalScope:生命周期application级别的,进程级别的
* MainScope:生命周期activity级别的 。可以通过实现 CoroutineScope by MainScope() 使用
* viewModelScope:生命周期绑定viewModel
* lifecycleScope:生命周期绑定viewModellifecycle
*
*/
val mainScope = MainScope()
fun 结构化并发() {
mainScope.launch {
try {
//如果在delay 10秒的过程中取消 mainScope.cancel() 会报异常。
//delay 函数是个挂起函数,相当于子协程,取消协程 对子协程的影响,以及一个子协程取消了,其它子协程会怎么样?
delay(10000)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
override fun onDestroy() {
super.onDestroy()
mainScope.cancel()
}
/**
*协程构建器:
* 1、launch
* 2、async
*/
fun 协程构建器() = runBlocking {//插曲:runBlocking可以把当前主线程 变成协程 是阻塞得,等待子协程执行完毕 ,才会退出
val job1 = launch {
println("job1 finished")
}
val job2 = async {
println("job2 finished")
"job2 result"
}
//await是可以拿到协程返回值得"job2 result"
println(job2.await())
//打印结果 :job1 finished job2 finished job2 result
}
fun 协程得join和await方法的等待协程作业() = runBlocking {
val job1 = launch {
println("job1 finished")
delay(2000)
}
//join方法,只有job1执行完毕,才会执行job2和job3子协程
job1.join()
val job2 = launch {
println("job1 finished")
delay(1000)
}
val job3 = launch {
println("job1 finished")
delay(1000)
}
//==============awiat
val jobd1 = async {
println("job1 finished")
delay(2000)
}
//await方法(await可以拿到协程执行结果),jobd1,才会执行jobd2和jobd3子协程
jobd1.await()
val jobd2 = async {
println("job1 finished")
delay(1000)
}
val jobd3 = async {
println("job1 finished")
delay(1000)
}
}
fun async组合并发() = runBlocking {
val times = measureTimeMillis {
val one = doOne()
val two = doTwo()
println("计算结果:${one + two}")
}
println("执行耗时:$times")
//执行耗时:2秒
//async组合并发 写法
val times2 = measureTimeMillis {
val job1 = async { doOne() }
val job2 = async { doTwo() }
println("计算结果:${job1.await() + job2.await()}")
}
println("执行耗时:$times")
//执行耗时:1秒
}
private suspend fun doOne(): Int {
delay(1000)
return 10
}
private suspend fun doTwo(): Int {
delay(1000)
return 12
}
/**
*协程的启动模式
*>DEFAULT:协程创建后,立即开始调度,在调度前如果协程被取消,其将直接进入取消
*响应的状态。
*>ATOMIC:协程创建后,立即开始调度,协程执行到第一个挂起点之前不响应取消。
*>LAZY:只有协程被需要时,包括主动调用协程的start、join或者await等函数时才会开始
*调度,如果调度前就被取消,
*那么该协程将直接进入异常结束状态。
*>UNDISPATCHED:协程创建后立即在当前函数调用栈中执行,直到遇到第一个真正挂起
*的点。
*
*
* 这里调度 和执行的逻辑,有点想消息队列
* 调度是把 这个协程加入到队列里 等待执行
* 所以立即调度和立即执行 不是一个意识
*
*/
suspend fun 协程启动模式() = runBlocking {
val job1 = launch(start = CoroutineStart.DEFAULT) {
delay(10000)
println("job1")
}
delay(1000)
job1.cancel()
val job2 = launch(context = Dispatchers.IO, start = CoroutineStart.UNDISPATCHED) {
//如果使用UNDISPATCHED启动模式,下面会打印的是主线程 因为当前函数栈是主线程
//因为UNDISPATCHED模式:UNDISPATCHED:协程创建后立即在当前函数调用栈中执行,直到遇到第一个真正挂起的点
println(Thread.currentThread().name)
}
job2.join()
}
/**
*作用域构建器:coroutineScope 只要当前作用域内 子协程 有一个发生异常了,所有协程都会被取消
*作用域构建器:supervisorScope 作用域内不会因为某个子协程的失败,而取消其它兄弟 协程
* coroutineScope是创建一个非堵塞的协程作用域,而 runBlocking是阻塞。
*
*/
suspend fun 作用域构建器() = runBlocking {
coroutineScope {// =协程作用域构建器
val job1 = async {
delay(400)
println("job1..")
}
val job2 = async {
delay(200)
println("job2..")
throw IllegalAccessException()
}
}
supervisorScope {
val job1 = async {
delay(200)
println("job1..")
}
val job2 = async {
delay(200)
println("job2..")
throw IllegalAccessException()
}
}
}
/**
* Job对象
*>对于每一个创建的协程(通过launch或者async),会返回一个Job实例,该实例是协程
*的唯一标示,并且负责管理协程的生命周期。
*>一个任务可以包含一系列状态:新创建(New)、活跃(Active)、完成中(Completi
*ng)、已完成(Completed)、取消中(Cancelling)和已取消(Cancelled)。虽然
*我们无法直接访问这些状态,但是我们可以访问)ob的属性:isActive、isCancelled和is
*Completed
*/
suspend fun job生命周期() = runBlocking {
val job = launch {
delay(10000)
}
job.isActive
job.isCancelled
job.isCompleted
job.cancel()
}
/**
*
* 协程的取消
*>取消作用域会取消它的子协程。
*CANCEL
*>被取消的子协程并不会影响其余兄弟协程。
*>协程通过抛出一个特殊的异常CancellationException来处理取消操作。
*>所有kotlinx.coroutines中的挂起函数(withContext、delay等)都是可取消的。
*
*===================================================
* 拓展知识:CoroutineScope和小写coroutineScope得区别,都是构建了协程作用域,用于结构化并发管理协程
* 但是coroutineScope更想是上层封装好得api,这个协程作用域构建器继承了父协程构建器得协程作用域
* 而CoroutineScope构建得协程作用域是自己创建得
*
*所以当runblocking 里不加入delay得时候,上面得两个launch无法得到执行
* 因为他们得协程作用域不同,runBlocking本身又是阻塞得,子协程执行完了,也就结束了。
* 但是CoroutineScope创建得两个子协程并不是runblocking得,
* 所以runblocking就认为没有需要执行得协程,没有继续阻塞,直接结束当前方法。
* 所以只有delay一下,给CoroutineScope得协程执行得时间。
*/
suspend fun 取消协程_取消作用域() = runBlocking<Unit> {
val scope = CoroutineScope(Dispatchers.Default)
scope.launch {
delay(1000)
println("job 1")
}
scope.launch {
delay(1000)
println("job 2")
}
delay(100)
scope.cancel()//取消携程作用域 会取消子协程
delay(2000)
}
suspend fun 取消协程_被取消得协程并不会影响兄弟协程() = runBlocking<Unit> {
val scope = CoroutineScope(Dispatchers.Default)
val job1 = scope.launch {
delay(1000)
println("job 1")
}
val job2 = scope.launch {
delay(1000)
println("job 2")
}
delay(100)
job1.cancel()//取消携子协程job1,不会影响子协程job2
delay(2000)
}
/**
* 拓展知识:
* 协程取消cancel的时候,会抛出一个CancellationException异常。如果不去捕捉,程序也不会奔溃,会被
* 静默处理了。
*/
suspend fun 取消协程_取消协程抛出的异常() = runBlocking<Unit> {
val job = GlobalScope.launch {
try {
delay(10000)
println("job...")
} catch (e: Exception) {
e.printStackTrace()
}
}
// job.cancel(CancellationException("asd")) //cancel也可以指定异常
job.cancelAndJoin()
}
/**
*CPU密集性任务是无法使用cancel方法取消的
*
*
* 第一种:isActive 标志位 判断
* 但是可以利用cancel之后,生命周期的状态改变,就行判断
*可以通过协程得生命周期属性 isActive 来进行判断 是否继续执行协程里得代码。
*
* 第二种:ensureActive() 使用这种方式会抛个异常,也会被静默处理掉
* 第三种:yield() 出让协程执行权
*/
suspend fun CPU密集性任务取消() = runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.IO) {
var nextPrintTime = startTime
var i = 0
//第一种:
while (i < 5 && isActive) {
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500
}
}
//第二种:
while (i < 5) {
ensureActive()
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500
}
}
//第三种:
while (i < 5) {
yield()
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500
}
}
}
delay(1300)
println("main I'm tried of wating!")
job.cancelAndJoin()
println("main: Now I can quit.")
}
/**
* 在取消协程的时候,可能会导致一些资源没有被释放。
* 可以通过try catch finally进行释放
*
* 如果是io流操作 可以用use函数操作 use函数会自动关闭IO流
*/
suspend fun 协程取消的副作用() = runBlocking {
//第一种:
val br = BufferedReader(
FileReader(
Environment.getExternalStorageDirectory().toString() + "/a.txt"
)
)
with(br) {
var line: String?
try {
while (true) {
line = readLine() ?: break
println(line)
}
} finally {
close()
}
}
//第二种:
br.use {
var line: String?
while (true) {
line = it.readLine() ?: break
println(line)
}
}
}
/**
* 如果协程被取消了,里面还有子协程,我们不想取消子协程。那么可以使用
* withContext(NonCancellable){}
*/
suspend fun 不能被取消的协程任务() = runBlocking {
val job = launch {
//下面就是常驻任务,不能因为调用cancel而被取消
withContext(NonCancellable) {
}
}
job.cancelAndJoin()
}
/**
* 协程内部如果有超时任务,会报异常
* 可以使用withTimeoutOrNull
*
* 使用场景,执行一个耗时任务,如果超时了,我们就取消任务,并且返回一个值
*/
suspend fun 超时任务() = runBlocking {
val result = withTimeoutOrNull(1300) {
repeat(1000) {
delay(500)
}
} ?: "超时了"
println("$result")
}
/**
* CoroutineContext 是一组用于定义协程行为的元素。由以下结构组成:
* Job:控制协程的生命周期
* CoroutineDispatcher:向合适的线程分发任务
* CoroutineName:协程的名称,调试的时候有用
* CoroutineExceptionHandler:处理未被捕获的异常
*
* 可以用 加号( + ) 进行组合 得到CoroutineContext
*/
suspend fun 协程上下文() = runBlocking {
val job = launch(Job() + Dispatchers.Default + CoroutineName("asd")) {
println("")
}
}
/**
*对于新创建的协程,它的CorountineContext会包含一个全新的Job实列,它会帮助
* 我们控制协程的生命周期。而剩余的元素会从CorountineContext的父类继承,该父类可能
* 是另外一个协程或者创建该协程的CorountineScope(协程作用域)。
*
* 所以job1打印的coroutineContext[Job] job对象 和job1_child的job对象是不一样的
* 但是CoroutineName是一样的
*/
suspend fun 协程上下文的继承() = runBlocking {
val coroutineExceptionHandler = CoroutineExceptionHandler { _, e ->
println(e.message)
}
val scope =
CoroutineScope(Job() + Dispatchers.IO + CoroutineName("我的协程")) + coroutineExceptionHandler
val job1 = scope.launch {
println("i am corountine ${coroutineContext[Job]} ${Thread.currentThread()}")
val job1_child = async {
println("i am corountine ${coroutineContext[Job]} ${Thread.currentThread()}")
}
}
}
/**
*如果继承了协程上下文,又重新覆盖了某个部分,会被覆盖掉,不会使用父协程的上下文
* 所以下面打印的是 IO线程
*/
suspend fun 协程上下文的继承_2() = runBlocking {
val coroutineExceptionHandler = CoroutineExceptionHandler { _, e ->
println(e.message)
}
val scope = CoroutineScope(Job() + Dispatchers.Main + coroutineExceptionHandler)
val job = scope.launch(Dispatchers.IO) {
println("${Thread.currentThread()}")
}
}
/**
* 协程构建器有两种形式:自动传播异常(launch和actor),向用户
* 暴露异常(async和produce)当这些构建器用于创建一个根协程时(该协程
* 不是另一个协程的子协程),前者这类构建器,异常会在它发生的第一时间被抛出,
* 后者则依赖用户最终消费异常,类如通过await和receive。
*
* *** 一定是根协程才符合上面异常的发生规律 ***
*
* 所以我们捕捉异常的地方也不一样,如下代码:
*/
suspend fun 协程异常的自动传播和主动暴露_根协程() = runBlocking<Unit> {
val job = GlobalScope.launch {
try {
throw IndexOutOfBoundsException()
} catch (e: Exception) {
println("IndexOutOfBoundsException")
}
}
job.join()
val deferred = GlobalScope.async {
throw NullPointerException()
}
try {
deferred.await()
} catch (e: Exception) {
println("NullPointerException")
}
}
/**
*非根协程所创建的协程中,产生的异常总是会被传播
*
* 即使是async构建器,不调用await的情况下,异常也会被传播
*/
suspend fun 协程异常的自动传播和主动暴露_非根协程() = runBlocking<Unit> {
val scope = CoroutineScope(Job())
val job = scope.launch {
async {
throw ArrayIndexOutOfBoundsException()
//如果async 抛出异常,launch 就会立即执行抛出异常,
//而不用调用await()
}
}
job.join()
}
/**
* 当一个协程由于一个异常而运行失败时,
* 它会传播这个异常并传递给它的
* 父级。接下来,父级会进行下面几个操作:
* 1、取消它自己的子级
* 2、取消它自己
* 3、将异常传播并传递给它的父级
*
* 按照上面的逻辑 就会导致一个子协程的异常,导致整个协程都被取消
* 我们可以通过SupervisorJob 打 破整个规律。不会因为一个子协程的异常,
* 导致其它子协程都被取消(应该和supervisorScope一个道理,一个是作用域构建器,
* 一个是利用CoroutineScope作用域构建器的上下文对象中的job对象。
* 后续:查阅资料后 supervisorScope构建的协程可以达到使用SupervisorJob一个效果,
* 但是如果supervisorScope内部发生错误,会导致所有内部子协程都被取消 举列见://supervisorScope举列)
*
* 如果要停止所有协程,可以用supervisor.cancel()方法
*/
suspend fun 协程异常的传播特性() = runBlocking {
val supervisor = CoroutineScope(SupervisorJob())
val job1 = supervisor.launch {
delay(100)
println("child 1")
throw IllegalAccessException()
}
val job2 = supervisor.launch {
try {
delay(Long.MAX_VALUE)
} finally {
println("child 2 finished...")
}
}
joinAll(job1, job2)
//此处不会因为job1的异常,导致job2也被取消
//supervisorScope举列
supervisorScope {
val job1 = launch {
delay(100)
println("child 1")
throw IllegalAccessException()
}
//如果作用域本身发生异常了,那么job1和job2也会被取消
throw ArrayIndexOutOfBoundsException()
val job2 = launch {
try {
delay(Long.MAX_VALUE)
} finally {
println("child 2 finished...")
}
}
}
}
/**
* 使用CoroutineExceptionHandler对协程进行异常捕捉
* 以下的条件满足时,异常就会被捕获:
* 1、时机:异常是被自动抛出的(比如launch可以,async就不行)
* 2、位置:在CorountineScope的CorountineContext中或在一个根协程(CorountineScope或者supervisorScope的
* 直接子协程)中。
*
*
* 所以下面的代码 只能捕获到job的异常,而无法捕捉到deferred的
*/
suspend fun 异常捕获的时机() = runBlocking<Unit> {
val handler = CoroutineExceptionHandler { _, ex ->
println(ex.message)
}
val job = launch(handler) {
throw ArrayIndexOutOfBoundsException()
}
val deferred = async(handler) {
throw IllegalAccessException()
}
job.join()
deferred.await()
}
/**
* 使用CoroutineExceptionHandler对协程进行异常捕捉
* 以下的条件满足时,异常就会被捕获:
* 1、时机:异常是被自动抛出的(比如launch可以,async就不行)
* 2、位置:在CorountineScope的CorountineContext中或在一个根协程(CorountineScope或者supervisorScope的
* 直接子协程)中。
*
*
* 所以下面的代码 方式一可以捕获到异常,方式二捕获不到。
* 方式一 handler设置的地方 符合 在CorountineScope的CorountineContext中。也符合时机 launch
* 方式二 不符合上述条件
*
* 结论:handler最好放在外部协程里,不要放入内部协程(其实之前学习异常传播特性 我们应该也可以联想到,
* 内部协程的异常 是向外部传输的。所以异常自然 也是在最外部去捕捉)
*
*
*/
suspend fun 异常捕获的时机_2() = runBlocking<Unit> {
val handler = CoroutineExceptionHandler { _, ex ->
println(ex.message)
}
val scope = CoroutineScope(Job())
//捕捉方式一:
val job1 = scope.launch(handler) {
launch {
throw ArrayIndexOutOfBoundsException()
}
}
job1.join()
//捕获方式二:
val job2 = scope.launch {
launch(handler) {
throw ArrayIndexOutOfBoundsException()
}
}
job2.join()
}
/**
* 只能捕获异常,但是不能解决 程序奔溃的问题。这里只是得到异常信息
*
* main(目录)->resources(目录)->META-INF(目录)->services(目录)
* ->kotlinx.corountines.CorountineExceptionHandler(文件)
*
* 创建自定义CoroutineExceptionHandler->MyCoroutineExceptionHandler
*
* kotlinx.corountines.CorountineExceptionHandler文件里写上
*com.corrountine.MyCoroutineExceptionHandler(就是MyCoroutineExceptionHandler的全路径)
*
*
*/
suspend fun 协程全局异常捕捉() = runBlocking {
}
/**
* 取消与异常
* 取消与异常紧密相关,协程内部使用CancellationException:来进行取消,这个异常会被
* 忽略。
* 当子协程被取消时,不会取消它的父协程。
* 如果一个协程遇到了CancellationException以外的异常,它将使用该异常取消它的父协
* 程。当父协程的所有子协程都结束后,异常才会被父协程处理。
*
* 举例一:不会因为child的取消抛出的异常,导致父协程job被取消
*
* 举例二:非CancellationException的异常,子协程都被取消了,父协程才会去处理异常
*
*/
suspend fun 取消与异常() = runBlocking {
//举例一:
val job = launch {
val child = launch {
try {
delay(Long.MAX_VALUE)
} finally {
println("Child is cancelled.")
}
}
yield()
println("Cancelling child")
child.cancelAndJoin()
yield()
println("Parent is no cancelled")
}
job.join()
//举例二:
val handler = CoroutineExceptionHandler { _, ex ->
println("Caugth:${ex.message}")
}
val job2 = GlobalScope.launch(handler) {
launch {
try {
delay(Long.MAX_VALUE)
} finally {
//常驻协程,不会被取消
withContext(NonCancellable) {
println("子协程都被取消了,但是异常还没有被处理,要等所有的子协程都被取消了,才会被父协程处理")
delay(100)
println("第一个子协程处理完毕了")
}
}
}
launch {
delay(10)
println("第二个子协程抛出异常")
throw ArrayIndexOutOfBoundsException()
}
}
job2.join()
//job2 打印结果
// 第二个子协程抛出异常
// ->子协程都被取消了,但是异常还没有被处理,要等所有的子协程都被取消了,才会被父协程处理
// ->第一个子协程处理完毕了
// ->Caugth:ArrayIndexOutOfBoundsException
}
/**
* 异常聚合
* 当协程的多个子协程因为异常而失败时,一般情况下取第一个异常进行处理。在
* 第一个异常之后发生的所有其他异常,都将被绑定到第一个异常之上。
*
* ex.suppressed.contentToString()是个数组,异常都会在里面
*/
suspend fun 异常的聚合() = runBlocking {
val handler = CoroutineExceptionHandler { _, ex ->
println("Caugth:${ex.suppressed.contentToString()}")
}
val job = GlobalScope.launch(handler) {
launch {
try {
delay(Long.MAX_VALUE)
} finally {
throw ArithmeticException()
}
}
launch {
try {
delay(Long.MAX_VALUE)
} finally {
throw ArrayIndexOutOfBoundsException()
}
}
launch {
try {
delay(Long.MAX_VALUE)
} finally {
throw IllegalAccessException()
}
}
}
}
}
记录学习,仅供参考