IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Kotlin学习:5.1.协程是什么? -> 正文阅读

[移动开发]Kotlin学习:5.1.协程是什么?

一、什么是协程?

协程就是对线程的封装,片面理解为上层框架

Kotlin | 协程是什么?https://blog.51cto.com/petterp/4991838

1、解决的问题?

  1. 耗时
  2. 保证主线程安全
  3. 异步逻辑同步化,这种方式就借助Kt的编译器

2、基本实现

    @Test
    fun testCoroutine() = runBlocking {
        val job = GlobalScope.launch {
            delay(1000)
            println("这就是协程")
        }
        job.join()
    }

3、挂起和恢复

  • suspend 挂起
    suspend 用于暂停执行当前协程,并保存所有局部变量,被标记为 suspend 的函数只能运行在协程或者其他 suspend 函数
    协程的挂起本质是切线程,只是结束的时候还能切回来

  • resume 恢复
    用于让已暂停的协程从其暂停处继续执行

  • 挂起和堵塞
    挂起不会堵塞主线程,堵塞会

挂起函数使用 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、相同和不同点

launchasyncrunBlocking
使用场景不关心返回值关心返回值单元测试
堵塞线程不堵塞不堵塞堵塞
堵塞协程默认不堵塞默认不堵塞
未捕获异常崩溃不崩溃,且收集异常
所在协程启动后默认立即执行默认立即执行
对比线程类似Thread/Runable类似Future/Callable
返回值JobDeferred

2、Job (控制协程生命周期)

当我们创建一个协程的时候,会返回一个Job对象,不管是通过返回值管理,还是通过 launch 的构造方法的形式管理,其实是一样的。

我们通过Job就可以获取当前协程的运行状态,还可以随时取消协程。

在这里插入图片描述

方法用途
join阻塞并等候当前协程完成
await阻塞并等候当前协程完成,且获取结果
cancel取消协程
startstart 用于启动一个协程,让其到达Active状态
invokeOnCompletioninvokeOnCompletion 添加一个监听,当工作完成或者异常时会调用
isActive活跃
isCompleted已完成
isCancelled已取消
cancelAndJoin让当前协程等待job的执行,同时取消job的执行

协程不是默认创建就启动了吗? 怎么还有一个 start 方法 。
其实协程默认是启动的,但是我们可以创建一个懒加载的协程,手动start才开启协程。

3、CoroutineStart (启动模式)

启动模式调度取消
CoroutineStart.DEFAULT协程创建后立即开始调度(不一定此时就被线程执行了)在被线程执行前如果协程被取消,其将直接进入取消响应状态
CoroutineStart.ATOMIC协程创建后立即开始调度内部代码执行到第一个挂起点之前不响应取消操作(内部第一个挂起函数之前的代码一定执行)
CoroutineStart.LAZY只要协程被需要时(包括主动调用 start()、join()、await())才会开始调度如果调度前被取消,协程将进入异常结束状态
CoroutineStart.UNDISPATCHED协程被创建后立即在当前函数调用栈中执行直到内部代码执行到第一个挂起点,挂起函数运行完后,之后的代码就是在Dispatcher指定的线程中运行了。
  • 例:CoroutineStart.LAZY
    @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、协程的取消规则

  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)
        //        scope.cancel()
        delay(2000) // runBlocking 等待 scope 的子协程执行完毕
    }
================================ Thread[Test worker @coroutine#1,5,main]
job 1
job 2

如果将注释 scope.cancel() 打开

就只会输出

================================ Thread[Test worker @coroutine#1,5,main]
  1. 取消子协程不会影响兄弟协程
    @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) // runBlocking 等待 scope 的子协程执行完毕
    }

输出:

================================ 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()
//        delay(2000) // runBlocking 等待 scope 的子协程执行完毕
    }
  1. 协程是通过一个特殊的异常来取消操作 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) // runBlocking 等待 scope 的子协程执行完毕
    }

输出:

================================ Thread[Test worker @coroutine#1,5,main]
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job="coroutine#2":StandaloneCoroutine{Cancelling}@7d5dc9a4
job 2

JobCancellationException 是退出异常,协程会静默处理,不会提示出来

async 函数的取消

  1. 挂起函数(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
  • 解决方案1:isActive
    @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
  • 解决方案2:ensureActive
    @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)被静默处理

  • 解决方案3:yield
    @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、协程取消副作用

退出时需要释放资源,下面是释放资源的方法

  • finally
    @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?
//            try { // 这种写法直接提示 用use
//                while (true) {
//                    line = readLine() ?: break
//                    println(line)
//                }
//            } finally {
//                close()
//            }
//        }

        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//kotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:184)
	at app//kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:154)

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 {
//            try {
                throw NullPointerException("async")
//            } catch (e: Exception) {
//                println(e)
//            }
        }
        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

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-09-13 11:27:25  更:2022-09-13 11:28:40 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 4:36:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码