一、什么是协程?
协程就是对线程的封装,片面理解为上层框架
Kotlin | 协程是什么?https://blog.51cto.com/petterp/4991838
1、解决的问题?
- 耗时
- 保证主线程安全
- 异步逻辑同步化,这种方式就借助Kt的编译器
2、基本实现
@Test
fun testCoroutine() = runBlocking {
val job = GlobalScope.launch {
delay(1000)
println("这就是协程")
}
job.join()
}
3、挂起和恢复
挂起函数使用 suspend 关键字表示。 挂起函数能够以同步的方式写异步代码。 挂起函数拥有挂起和恢复的能力。 挂起函数的本质是 Callback,也就是 Continuation,Kotlin 编译器会完成 CPS 转换。 挂起函数只能在协程或者其他挂起函数中调用。
4、优势
异步逻辑同步化 非阻塞式挂起 == 不卡线程
5、协程的两部分
import kotlinx.coroutines.*
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.createCoroutine
使用基础层代码创建协程
val continuation = suspend { }.createCoroutine(object : Continuation<Unit> {
override val context: CoroutineContext
get() = TODO("Not yet implemented")
override fun resumeWith(result: Result<Unit>) {
TODO("Not yet implemented")
}
})
6、Dispatchers(调度器)
调度器 | 线程 | 用途 |
---|
Dispatchers.Main | 运行于主线程 | 处理UI交互等轻量级任务:调用suspend函数、调用UI函数、更新数据 | Dispatchers.Unconfined | 不改变线程 | | Dispatchers.Default (默认调度器) | 运行于线程池 | 专为CPU密集型计算任务进行了优化;2 ≤ 线程数 ≤ CPU数量;用于:数组排序、数据解析、处理差价判断 | Dispatchers.IO | 运行于线程池 | 专为阻塞任务进行了优化,默认限制可同时活跃的线程数 ≤ 64;用于:数据库、文件、网络 |
withContext(Dispatchers.IO) {}
withContext(Dispatchers.Main) {}
withContext(Dispatchers.Default) {}
7、任务泄露
当某个协程任务丢失,无法追踪,会导致CPU、磁盘资源浪费,甚至 发送无用的请求,这就是任务泄露
协程泄漏的本质是协程里的线程泄漏
8、结构性并发
为了避免内存泄漏,使用结构性并发
可以做到:取消任务、追踪任务、发出错误信号
具体的操作:9.协程作用域、二.4.协程作用域的构建器
9、CoroutineScope (协程作用域)
协程必须制定 CoroutineScope ,它会追踪所有协程,同样可以取消由它启动的所有的协程。
CoroutineScope 创建的协程(Job),并没有继承外部协程上下文。如果没有继承外部协程的上下文,这个时候外部协程就不会等待Job的执行结束,这个需要在外部协程中使用Job.join ,让外部协程等待job执行结束再向下执行。
fun CoroutineScope(context: CoroutineContext): CoroutineScope
CoroutineContext:协程的上下文
常用API
- GlobeScope:全局范围,不会自动结束执行
- MainScope:主线程的作用域,全局范围,在MainActivity使用,需要在DESTROYED时取消
private val mainScope = MainScope()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainScope.launch {
delay(1000)
println("协程 ${Thread.currentThread()}")
}
}
override fun onDestroy() {
super.onDestroy()
mainScope.cancel()
}
- lifecycleScope:生命周期范围,在DESTROYED的时候会自动结束,用于Activity和Fragment
- viewModelScope:viewModel范围,用于ViewModel中,在ViewModel被回收时会自动结束
二、协程的启动
1、协程构建器
1.1、launch
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job
launch 用来启动一个子协程并立即返回,协程像线程一样异步执行;
launch 协程中的未捕获异常会导致进程的crash;
launch 挂起时不会堵塞所在协程,通过Job.join方法会让所在协程等待挂起结束;
launch 更多是用来发起一个无需结果的耗时任务,这个工作不需要返回结果。
1.2、async
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyDeferredCoroutine(newContext, block) else
DeferredCoroutine<T>(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
async用来启动一个协程并返回一个Deferred,Deferred 继承自 Job 接口,Job有的它都有,增加了一个方法 await , 这个方法接收的是 async 闭包中返回的值,所以async相当于可以返回结果的launch。
async 挂起时会阻塞所在协程
另外,async默认启动协程后立即执行,但是也可以通过参数指定启动方式为CoroutineStart.LAZY,此时只有调用了await时,才会启动协程。
async内的代码中未捕获异常不会造成进程crash,而是会被储存到Deferred中返回。
async 函数则是更进一步,用于异步执行耗时任务,并且需要返回值(如网络请求、数据库读写、文件读写),在执行完毕通过 await() 函数获取返回值。
当需要同步获取子协程的执行结果时使用async,不关心子协程的处理结果时使用launch。可以类比到线程中Thread/Runable和Future/Callable在使用场景的区别。
1.3、runBlocking
runBlocking启动的协程任务会阻断当前线程,直到该协程执行结束。当协程执行结束之后,页面才会被显示出来。
runBlocking 通常适用于单元测试的场景,而业务开发中不会用到这个函数
1.4、相同和不同点
| launch | async | runBlocking |
---|
使用场景 | 不关心返回值 | 关心返回值 | 单元测试 | 堵塞线程 | 不堵塞 | 不堵塞 | 堵塞 | 堵塞协程 | 默认不堵塞 | 默认不堵塞 | | 未捕获异常 | 崩溃 | 不崩溃,且收集异常 | | 所在协程启动后 | 默认立即执行 | 默认立即执行 | | 对比线程 | 类似Thread/Runable | 类似Future/Callable | | 返回值 | Job | Deferred | |
2、Job (控制协程生命周期)
当我们创建一个协程的时候,会返回一个Job对象,不管是通过返回值管理,还是通过 launch 的构造方法的形式管理,其实是一样的。
我们通过Job就可以获取当前协程的运行状态,还可以随时取消协程。
方法 | 用途 |
---|
join | 阻塞并等候当前协程完成 | await | 阻塞并等候当前协程完成,且获取结果 | cancel | 取消协程 | start | start 用于启动一个协程,让其到达Active状态 | invokeOnCompletion | invokeOnCompletion 添加一个监听,当工作完成或者异常时会调用 | isActive | 活跃 | isCompleted | 已完成 | isCancelled | 已取消 | cancelAndJoin | 让当前协程等待job的执行,同时取消job的执行 |
协程不是默认创建就启动了吗? 怎么还有一个 start 方法 。 其实协程默认是启动的,但是我们可以创建一个懒加载的协程,手动start才开启协程。
3、CoroutineStart (启动模式)
启动模式 | 调度 | 取消 |
---|
CoroutineStart.DEFAULT | 协程创建后立即开始调度(不一定此时就被线程执行了) | 在被线程执行前如果协程被取消,其将直接进入取消响应状态 | CoroutineStart.ATOMIC | 协程创建后立即开始调度 | 内部代码执行到第一个挂起点之前不响应取消操作(内部第一个挂起函数之前的代码一定执行) | CoroutineStart.LAZY | 只要协程被需要时(包括主动调用 start()、join()、await())才会开始调度 | 如果调度前被取消,协程将进入异常结束状态 | CoroutineStart.UNDISPATCHED | 协程被创建后立即在当前函数调用栈中执行 | 直到内部代码执行到第一个挂起点,挂起函数运行完后,之后的代码就是在Dispatcher指定的线程中运行了。 |
@Test
fun testLaunch() = runBlocking {
println("================================")
val job2 = async(start = CoroutineStart.LAZY) {
delay(1000)
println("async: ")
"job2"
}
val job1 = launch {
println("launch: ")
}
}
输出:
================================
launch:
如果调用,job2 .start,job2 就会启动
- 例:CoroutineStart.UNDISPATCHED
private val mainScope = MainScope()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
println("主线程 ${Thread.currentThread()}")
mainScope.launch(Dispatchers.IO) {
delay(1000)
println("协程 ${Thread.currentThread()}")
launch (start = CoroutineStart.UNDISPATCHED) {
println("协程 UNDISPATCHED ${Thread.currentThread()}")
}
launch (start = CoroutineStart.DEFAULT) {
println("协程 DEFAULT ${Thread.currentThread()}")
}
launch (start = CoroutineStart.ATOMIC) {
println("协程 ATOMIC ${Thread.currentThread()}")
}
}
mainScope.launch(Dispatchers.IO,start = CoroutineStart.UNDISPATCHED){
println("协程 UNDISPATCHED 2 ${Thread.currentThread()}")
}
}
I/System.out: 主线程 Thread[main,5,main]
I/System.out: 协程 UNDISPATCHED 2 Thread[main,5,main]
I/System.out: 协程 Thread[DefaultDispatcher-worker-1,5,main]
I/System.out: 协程 UNDISPATCHED Thread[DefaultDispatcher-worker-1,5,main]
I/System.out: 协程 DEFAULT Thread[DefaultDispatcher-worker-3,5,main]
I/System.out: 协程 ATOMIC Thread[DefaultDispatcher-worker-3,5,main]
4、协程作用域构建器
@Test
fun testCoroutineScope() = runBlocking {
println("================================ ${Thread.currentThread()}")
coroutineScope {
}
}
coroutineScope 就是我们的协程作用域构建器
作用域构建器 | 特点 |
---|
coroutineScope | 挂起函数,子协程失败了,其他子协程也取消 | runBlocking | 阻塞当前线程 | supervisorScope | 挂起函数,子协程失败了,不影响其他子协程 |
CoroutineScope 是 协程作用域,但不是构建器
5、取消协程
5.1、协程的取消规则
- 取消作用域会取消子协程
@Test
fun testCoroutineScope() = runBlocking {
println("================================ ${Thread.currentThread()}")
val scope = CoroutineScope(Dispatchers.Default)
scope.launch {
delay(1000)
println("job 1")
}
scope.launch {
delay(1000)
println("job 2")
}
delay(100)
delay(2000)
}
================================ Thread[Test worker @coroutine#1,5,main]
job 1
job 2
如果将注释 scope.cancel() 打开
就只会输出
================================ Thread[Test worker @coroutine#1,5,main]
- 取消子协程不会影响兄弟协程
@Test
fun testCoroutineScope() = runBlocking {
println("================================ ${Thread.currentThread()}")
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()
delay(2000)
}
输出:
================================ Thread[Test worker @coroutine#1,5,main]
job 2
换种写法展示 cancelAndJoin 的用途,如下代码等同于上面代码
@Test
fun testCoroutineScope() = runBlocking {
println("================================ ${Thread.currentThread()}")
val scope = CoroutineScope(Dispatchers.Default)
val job1 = scope.launch {
delay(1000)
println("job 1")
}
val job2 = scope.async {
delay(1000)
println("job 2")
}
delay(100)
job1.cancelAndJoin()
job2.join()
}
- 协程是通过一个特殊的异常来取消操作 CancellationException
launch 函数的取消
@Test
fun testCoroutineScope() = runBlocking {
println("================================ ${Thread.currentThread()}")
val scope = CoroutineScope(Dispatchers.Default)
val job1 = scope.launch {
try {
delay(1000)
println("job 1")
}catch (e:Exception){
println(e)
}
}
val job2 = scope.launch {
delay(1000)
println("job 2")
}
delay(100)
job1.cancel()
delay(2000)
}
输出:
================================ Thread[Test worker @coroutine#1,5,main]
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job="coroutine#2":StandaloneCoroutine{Cancelling}@7d5dc9a4
job 2
JobCancellationException 是退出异常,协程会静默处理,不会提示出来
async 函数的取消
- 挂起函数(delay withContext)都可以取消
5.2、CPU 密集型任务的取消
什么是CPU密集型?什么是IO密集型?:https://blog.csdn.net/lifulian318/article/details/124200505
例,如下代码,取消不了协程的运行
@Test
fun testCancel() = runBlocking {
println("================================ ${Thread.currentThread()}")
val start = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var next = start
var i = 0
while (i < 6) {
if (System.currentTimeMillis() >= next) {
println("job i = ${i++}")
next += 500
}
}
}
delay(1000)
println("tired of waiting")
job.cancel()
println("quit")
}
输出:
================================ Thread[Test worker @coroutine#1,5,main]
job i = 0
job i = 1
job i = 2
tired of waiting
quit
job i = 3
job i = 4
job i = 5
@Test
fun testCancel() = runBlocking {
println("================================ ${Thread.currentThread()}")
val start = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var next = start
var i = 0
while (i < 6 && isActive) {
if (System.currentTimeMillis() >= next) {
println("job i = ${i++}")
next += 500
}
}
}
delay(1000)
println("tired of waiting")
job.cancel()
println("quit")
}
输出:
================================ Thread[Test worker @coroutine#1,5,main]
job i = 0
job i = 1
job i = 2
tired of waiting
quit
@Test
fun testCancel() = runBlocking {
println("================================ ${Thread.currentThread()}")
val start = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var next = start
var i = 0
while (i < 6) {
ensureActive()
if (System.currentTimeMillis() >= next) {
println("job i = ${i++}")
next += 500
}
}
}
delay(1000)
println("tired of waiting")
job.cancel()
println("quit")
}
ensureActive 是通过抛异常的方式退出协程的,异常(JobCancellationException)被静默处理
@Test
fun testCancel() = runBlocking {
println("================================ ${Thread.currentThread()}")
val start = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var next = start
var i = 0
while (i < 6) {
yield()
if (System.currentTimeMillis() >= next) {
println("job i = ${i++}")
next += 500
}
}
}
delay(1000)
println("tired of waiting")
job.cancel()
println("quit")
}
yield 是挂起协程,让协程放弃本次 cpu 执行机会让给别的协程,当线程空闲时再次运行协程
5.3、协程取消副作用
退出时需要释放资源,下面是释放资源的方法
@Test
fun testRelease() = runBlocking {
println("================================ ${Thread.currentThread()}")
val job = launch {
try {
repeat(1000) {
println("job:repeat $it")
delay(500)
}
} finally {
println("异常退出,处理后续")
}
}
delay(1300)
println("tired of wait")
job.cancelAndJoin()
println("quit")
}
job:repeat 0
job:repeat 1
job:repeat 2
tired of wait
异常退出,处理后续
quit
注意这里 finally 不支持异步操作,需要支持请看 4.不能取消的任务
- use 函数
只能被实现了Closeable的对象使用,程序结束的时候会自动调用close方法,适合文件对象
@Test
fun testUse() = runBlocking {
println("================================ ${Thread.currentThread()}")
val br = BufferedReader(FileReader("F:\\testUse.txt"))
with(br) {
var line: String?
use {
while (true) {
line = readLine() ?: break
println(line)
}
}
}
}
================================ Thread[Test worker @coroutine#1,5,main]
文件用于测试USE方法,看看行不行,============================,123456.
5.4、不能取消的任务
withContext(NonCancellable)
@Test
fun testRelease() = runBlocking {
println("================================ ${Thread.currentThread()}")
val job = launch {
try {
repeat(1000) {
println("job:repeat $it")
delay(500)
}
} finally {
withContext(NonCancellable) {
println("异常退出,处理后续")
delay(100)
println("可以去做后续请求 ==")
}
}
}
delay(1300)
println("tired of wait")
job.cancelAndJoin()
println("quit")
}
5.5、超时任务
withTimeout(3000)
@Test
fun testTimeout() = runBlocking {
println("================================ ${Thread.currentThread()}")
withTimeout(3000) {
repeat(1000) {
println("job:repeat $it")
delay(500)
}
}
}
================================ Thread[Test worker @coroutine#1,5,main]
job:repeat 0
job:repeat 1
job:repeat 2
job:repeat 3
job:repeat 4
job:repeat 5
Timed out waiting for 3000 ms
kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 3000 ms
(Coroutine boundary)
at com.yoshin.kt.kotlindemo20220713.ExampleUnitTest$testTimeout$1$1.invokeSuspend(ExampleUnitTest.kt:144)
at com.yoshin.kt.kotlindemo20220713.ExampleUnitTest$testTimeout$1.invokeSuspend(ExampleUnitTest.kt:141)
Caused by: kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 3000 ms
at app
at app
withTimeout(3000) 会抛出异常,并且不会被静默处理,如果不想抛出异常:
@Test
fun testTimeout() = runBlocking {
println("================================ ${Thread.currentThread()}")
val result = withTimeoutOrNull(3000) {
repeat(1000) {
println("job:repeat $it")
delay(500)
}
"Do"
}
println("result is $result")
}
================================ Thread[Test worker @coroutine#1,5,main]
job:repeat 0
job:repeat 1
job:repeat 2
job:repeat 3
job:repeat 4
job:repeat 5
result is null
三、协程的异常处理
1、协程的上下文
CoroutineContext 是一组定义协程行为的元素
它由以下构成:
- Job:控制协程的生命周期
- ContoutineDispatcher:调度器
- ContoutineName:协程名字
- ContoutineExceptionHahdler:处理未被捕获的异常
1.1、使用时
@Test
fun testCoroutineContext(): Unit = runBlocking {
println("================================ ${Thread.currentThread()}")
launch(
context = Dispatchers.Default + CoroutineName("这是协程的名字"),
start = CoroutineStart.DEFAULT
) {
println("i work in ${Thread.currentThread()}")
}
}
================================ Thread[Test worker @coroutine#1,5,main]
i work in Thread[DefaultDispatcher-worker-1 @这是协程的名字#2,5,main]
1.2、协程上下文的继承
协程的上下文是可以继承的,对于新创建的协程,它的 CoroutineContext 会包含一个全新的 Job 实例,它用来控制协程的生命周期,该协程的其他元素会从 CoroutineContext 的父类继承,父类可能是另外一个协程或者创建该协程的 CoroutineScope。
也就是说,每个协程的上下文中,Job 对象一定是不一样的,而其他元素是继承过来的,都是同一个对象。需要注意的是,每个元素也可以在子协程启动时单独指定,以覆盖父类中的配置。
@Test
fun test() = runBlocking<Unit> {
println("================================ ")
println("runBlocking : ${coroutineContext[Job]} ${coroutineContext[CoroutineName]} ${Thread.currentThread().name}")
val scope = CoroutineScope(Job() + Dispatchers.IO + CoroutineName("test"))
val job = scope.launch {
println("launch 父 : ${coroutineContext[Job]} ${coroutineContext[CoroutineName]} ${Thread.currentThread().name}")
launch {
println("launch child 1: ${coroutineContext[Job]} ${coroutineContext[CoroutineName]} ${Thread.currentThread().name}")
}
async {
println("async child 2: ${coroutineContext[Job]} ${coroutineContext[CoroutineName]} ${Thread.currentThread().name}")
"async"
}
}
job.join()
}
================================
runBlocking : "coroutine#1":BlockingCoroutine{Active}@26f480c6 null Test worker @coroutine#1
launch 父 : "test#2":StandaloneCoroutine{Active}@130ff91f CoroutineName(test) DefaultDispatcher-worker-1 @test#2
launch child 1: "test#3":StandaloneCoroutine{Active}@605ba4d6 CoroutineName(test) DefaultDispatcher-worker-3 @test#3
async child 2: "test#4":DeferredCoroutine{Active}@5b89b6bd CoroutineName(test) DefaultDispatcher-worker-1 @test#4
总结: 协程上下文 = 默认值 + 继承的CoroutineContext + 参数
2、协程的异常处理
异常处理器安装到协程上下文
@Test
fun test() = runBlocking<Unit> {
println("================================ ")
val coroutineExceptionHandler = CoroutineExceptionHandler { _, e ->
println(e)
}
val scope =
CoroutineScope(Job() + Dispatchers.IO + CoroutineName("test") + coroutineExceptionHandler)
val job = scope.launch {
println("launch")
}
job.join()
}
协程构建器不同,异常也不同
2.1、异常的传播(根协程)
launch和async是创建协程的2种方式,像launch创建的协程,异常是自动传播的,只要发生了异常,就会第一时间抛出;而async则是属于用户消费异常,只有用户调用await才能抛出异常
因此,launch和async在捕获异常的时候是不同的,launch需要在协程体内捕获异常,而async需要在调用await的时候,才能捕获异常
如下: launch 必须try、catch,否则运行即崩溃 async在不调用await时无需 try、catch,但是调用await时就需要try/catch了
@Test
fun testException() = runBlocking<Unit> {
val job = GlobalScope.launch {
try {
throw NullPointerException("launch")
} catch (e: Exception) {
println(e)
}
}
val deferred = GlobalScope.async {
throw NullPointerException("async")
}
try {
deferred.await()
} catch (e: Exception) {
println(e)
}
}
如上两个位置的其中一个 try/catch 即可。
2.2、非根协程的异常传播
非根协程中,异常会被传播
@Test
fun testException() = runBlocking<Unit> {
val job = launch {
try {
throw NullPointerException("launch")
} catch (e: Exception) {
println(e)
}
}
val deferred = async {
throw NullPointerException("async")
}
}
如下打印,async 因为是非根协程,异常就会直接被抛出,即使不调用await
java.lang.NullPointerException: launch
async
java.lang.NullPointerException: async
at com.yoshin.kt.kotlindemo20220713.ExampleUnitTest$testException$1$deferred$1.invokeSuspend(ExampleUnitTest.kt:189)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:284)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
2.3、异常的传播特性
2.4、打破循环特性
- SupervisorJob 发生异常时不上报,自己处理
@Test
fun testSupervisorJob() = runBlocking<Unit> {
val supervisor = CoroutineScope(SupervisorJob())
val job1 = supervisor.launch {
delay(100)
println(
"job1"
)
throw java.lang.NullPointerException("job1")
}
val job2 = supervisor.launch {
repeat(100) {
delay(100)
println("job2 $it")
}
}
joinAll(job1, job2)
}
打印:
job2 0
job1
Exception in thread "DefaultDispatcher-worker-1 @coroutine#2" java.lang.NullPointerException: job1
at com.yoshin.kt.kotlindemo20220713.ExampleUnitTest$testSupervisorJob$1$job1$1.invokeSuspend(ExampleUnitTest.kt:202)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
....
job2 1
job2 2
job2 3
job2 4
job2 5
job2 6
job2 7
job2 8
job2 9
...
- supervisorScope 构建器也可以达到同样效果: 发生异常时不上报,自己处理
@Test
fun testSupervisorScope() = runBlocking<Unit> {
supervisorScope {
val job1 = launch {
delay(100)
println(
"job1"
)
throw java.lang.NullPointerException("job1")
}
val job2 = launch {
repeat(100) {
delay(100)
println("job2 $it")
}
}
joinAll(job1, job2)
}
}
job1
Exception in thread "Test worker @coroutine#2" java.lang.NullPointerException: job1
at com.yoshin.kt.kotlindemo20220713.ExampleUnitTest$testSupervisorScope$1$1$job1$1.invokeSuspend(ExampleUnitTest.kt:220)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
...........
job2 0
job2 1
job2 2
job2 3
- SupervisorJob 和 supervisorScope
协程内部发生错误,会影响子协程,使子协程也终止
@Test
fun testSupervisorScope() = runBlocking<Unit> {
supervisorScope {
val job1 = launch {
delay(100)
println(
"job1"
)
}
val job2 = launch {
repeat(100) {
delay(100)
println("job2 $it")
}
}
delay(300)
throw NullPointerException("supervisorScope 异常")
}
}
job2 0
job1
Exception in thread "DefaultDispatcher-worker-1 @coroutine#2" java.lang.NullPointerException: job1
job2 1
supervisorScope 异常
java.lang.NullPointerException: supervisorScope 异常
2.5、异常的捕获
CoroutineExceptionHandler 适用于:根协程下异常自动传播的场景(lanuch)
@Test
fun testHandler() = runBlocking<Unit> {
val handler = CoroutineExceptionHandler { _, e ->
println(e)
}
val job = GlobalScope.launch (handler){
throw NullPointerException("launch")
}
val job2 = GlobalScope.async (handler){
throw NullPointerException("async")
}
job.join()
job2.await()
}
如下:launch的异常被捕获;async没有被捕获
java.lang.NullPointerException: launch
async
java.lang.NullPointerException: async
at com.yoshin.kt.kotlindemo20220713.ExampleUnitTest$testHandler$1$job2$1.invokeSuspend(ExampleUnitTest.kt:246)
(Coroutine boundary)
......
2.6、异常捕获常见错误
异常处理器要安装到外部协程上
如下:
@Test
fun testException3() = runBlocking<Unit> {
val handler = CoroutineExceptionHandler { _, e ->
println(e)
}
val scope = CoroutineScope(Job())
val job = scope.launch {
launch (handler){
throw NullPointerException("launch")
}
}
}
Exception in thread "DefaultDispatcher-worker-2 @coroutine#3" java.lang.NullPointerException: launch
at com.yoshin.kt.kotlindemo20220713.ExampleUnitTest$testException3$1$job$1$1.invokeSuspend(ExampleUnitTest.kt:282)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
2.7、协程的全局异常获取
可以捕获协程内产生,且没有被捕获的异常
写法: GlobalExceptionHandler 类:
package com.yoshin.kt.kotlindemo20220713
class GlobalExceptionHandler : CoroutineExceptionHandler {
override val key = CoroutineExceptionHandler
override fun handleException(context: CoroutineContext, exception: Throwable) {
Log.d(TAG, "handleException: $exception")
}
companion object {
private const val TAG = "GlobalExceptionHandler"
}
}
kotlinx.coroutines.CoroutineExceptionHandler 文件:
com.yoshin.kt.kotlindemo20220713.GlobalExceptionHandler
2.8、协程的取消和异常
取消子协程(产生CancellationException异常),不会影响父协程;子协程异常(CancellationException以外的异常),会影响父协程。
2.9、异常的聚合
多异常时,获取第二个第三个异常的方法
@Test
fun testExceptionAggregation() = runBlocking<Unit> {
val handler = CoroutineExceptionHandler { _, e ->
println("第一个异常 :$e")
println("第2个异常 :${e.suppressed.contentToString()}")
}
val job = GlobalScope.launch(handler) {
launch() {
try {
delay(Long.MAX_VALUE)
} finally {
throw NullPointerException("第2个")
}
}
launch {
delay(100)
throw NullPointerException("第一个")
}
}
job.join()
}
第一个异常 :java.lang.NullPointerException: 第一个
第2个异常 :[java.lang.NullPointerException: 第2个]
参考地址
bilibili:动脑学院
Kotlin 中文网 :https://www.kotlincn.net/docs/reference/coroutines-overview.html
kotlin之协程(六),协程中的 async和launch的区别以及runBlocking:https://www.jianshu.com/p/1dc67a300b6b
Kotlin协程的launch与async:https://blog.csdn.net/vitaviva/article/details/104103454
|