Kotlin协程 - - -上下文与异常处理

一.协程上下文

1.协程的上下文的组成

CoroutineContext是一组用于定义协程行为的元素。它由如下几项构成。
Job:控制协程的生命周期
CoroutineDispatcher:向合适的线程分发任务
CoroutineName:协程的名称,调试的时候很有用
CoroutineExceptionHandler:处理未被捕捉的异常

2.组合上下文中的元素

 @Test
 fun testCoroutineContext() = runBlocking {
               //这里把调度器和CoroutineName操作符相加
        launch(Dispatchers.Default + CoroutineName("test")) {
            println("I'm working in thread ${Thread.currentThread().name}")
        }
    }

打印结果:

I'm working in thread DefaultDispatcher-worker-1 @test#2

吃打印结果后面的名字@test#2 需要在test文件夹,进性单元测试才会出现。

3.协程上下文的继承

对于新创建的协程,它的CoroutineContext会包含一个全新的J0b实例,它会帮助我们控制协程的生命周期。而剩下的元素会的元素会从CoroutineContext的父类继承,该父类可能是另外一个协程或者创建该协程的CoroutineScope。

 @Test 
fun testCoroutineContextExtend() = runBlocking {
        var scope = CoroutineScope(Job() + Dispatchers.IO + CoroutineName("testExtend"))
        val launch = scope.launch {
            //launch为scope的子协程,会继承scope的上下文
            println("${coroutineContext[Job]}    ${Thread.currentThread().name}")
            val async = async {
                //async为launch的子协程,会继承scope的上下文
                println("${coroutineContext[Job]}    ${Thread.currentThread().name}")
                "OK"
            }.await()
        }
        launch.join()

    }

打印结果:

"testExtend#2":StandaloneCoroutine{Active}@6318c9c9    DefaultDispatcher-worker-1 @testExtend#2
"testExtend#3":DeferredCoroutine{Active}@11fd5300    DefaultDispatcher-worker-3 @testExtend#3

前面不同是因为,每个Job对象都不同。


协程的上下文=默认值+继承的CoroutineContext+参数
一些元素包含默认值:Dispatchers.DefauIt是默认的CoroutineDispatcher,以及“coroutine"
作为默认的CoroutineName;
继承的CoroutineContext是CoroutineScope或者其父协程的CoroutineContext;
传入协程构建器的参数的优先级高于继承的上下文参数,因此会覆盖对应的参数值。

 @Test 
fun testCoroutineContextExtend2() = runBlocking {
        val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception ->
            println("Caught:$exception")
        }
        val coroutineScope = CoroutineScope(
            Job() + Dispatchers.Main + coroutineExceptionHandler
        )
        //通过父作用域构建的协程如果没有重新定义会直接继承父类的参数,如自己定义的有就会用自己的                           
        //之前也说过job的对象每次都是不同的
        coroutineScope.launch(Dispatchers.IO) {

        }
    }

二.协程的异常处理

1.根协程的异常处理

协程构建器有两种形式:自动传播异常(launch与actor)向用户暴露异常(async与produce)当这些构建器用于创建一个根协程时(该协程不是另一个协程的子协程)前者这类构建器,异常会在它发生的第一时间被抛出,而后者则依赖用户来最终消费异常,例如通过await或receive。

//根协程异常
fun testExceptionPropagation() = runBlocking {
    //自动传播异常
    //launch抛得异常在
    val launch = GlobalScope.launch {
        try {
            throw  IndexOutOfBoundsException()  //这一行抛出
        } catch (e: Exception) {
            println("Caught IndexOutOfBoundsException")
        }

    }
    launch.join()
    //向用户传播异常
    //async抛得异常在

    val async = GlobalScope.async {
        throw ArrayIndexOutOfBoundsException()
    }
    try {
        //async在没有启动是这个异常是不会catch到
        async.await() //这一行抛出
    } catch (e: Exception) {
        println("Caught ArrayIndexOutOfBoundsException")
    }
}

打印结果:

Caught IndexOutOfBoundsException
Caught ArrayIndexOutOfBoundsException

通过上述代码说明,launch 的异常正常就能catch到,async的异常得在调用await方法时catch

2.非根协程异常处理

    //非根协程异常  的async会立即抛出异常
    fun testExceptionPropagation2() = runBlocking {
        val coroutineScope = CoroutineScope(Job())
        val launch = coroutineScope.launch {
            async {
                throw IndexOutOfBoundsException()
            }
        }
        launch.join()
    }

这段代码一运行就会报错,这里async作为子协程发生异常就会向上传,而launch无法处理异常就会报错.

3.异常的传播特性

当一个协程由于一个异常而运行失败时,他会传播这个异常并传递给它的父级,接下来父级会进行下面几步操作:

1.取消他自己的自级

2.取消他自己

3.将异常传递给它的父级

4.SupervisorJob

    @Test
    //使用SupervisorJob一个子协程出问题不会影响到其他的协程
    fun testSupervisorJob() = runBlocking {
        val coroutineScope = CoroutineScope(SupervisorJob())
        val launch = coroutineScope.launch {
            throw IndexOutOfBoundsException()
        }

        val launch1 = coroutineScope.launch {
            delay(1000)
            println("无异常")
        }
        //coroutineScope.cancel()//父作用域取消后,里面的子线程也会取消
        joinAll(launch, launch1)
    }
}

SupervisorJob得作用就是其中一个协程发生异常,其他子协程还会继续执行。

5.supervisorScope 

//supervisorScope中的作用域发生异常时,supervisorScope里面会全部失败
fun testSupervisorScope() = runBlocking {

    supervisorScope {

        //throw IndexOutOfBoundsException()
        val launch = launch {
            try {
                print("child 1 finished.")
                delay(Long.MAX_VALUE)
            } finally {

                print("child 2 finished.")
            }
        }
        yield()
        println("asdsadsafdsag")
        throw ClassNotFoundException()
    }
}

6.捕获异常

1.CoroutineExceptionHandler 

使用CoroutineExceptionHandler对协程的异常进行捕获。
以下的条件被满足时,异常就会被捕获:
时机异常是被自动抛出异常的协程所抛出的(使用launch,而不是async时);
位置在CoroutineScope的CoroutineContext中或在一个根协程(CoroutineScope或者supe
rvisorScope的直接子协程)中。

//launch会自动抛出异常所以能捕获到  还要在Scope构建根协程中
fun testCoroutineExceptionHandler() = runBlocking {
    val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception ->
        println("Caught $exception")
    }
    val job = GlobalScope.launch(coroutineExceptionHandler) {
        throw AssertionError() //能捕获到
    }

    val async = GlobalScope.async(coroutineExceptionHandler) {
        throw  ArithmeticException();//不能
    }
    job.join()
    async.await()
}

2.全局处理异常

全局异常处理器可以获取到所有协程未处理的未捕获异常,不过它并不能对异常讲行捕获,
虽然不能阻止程序崩溃,全局异常处理器在程序调试和异常上报等场景中仍然有非常大的
用处。
我们需要在classpath下面创建META-lNF/services目录,并在其中创建一个名为kot|inx.
coroutines.CoroutineExceptionHandler的文件,文件内容就是我们的全局异常处理器
的全类名。

class GlobalCoroutineExceptionHandler:CoroutineExceptionHandler {
    override val key= CoroutineExceptionHandler

    override fun handleException(context: CoroutineContext, exception: Throwable) {
       Log.d("ning","Unhandled Coroutine Exception: $exception")
    }
}

首先创建这个异常捕获类。

Kotlin协程 - - -上下文与异常处理_第1张图片

 随后创建kot|inx.coroutines.CoroutineExceptionHandler文件

com.example.kotlincoroutineexception.GlobalCoroutineExceptionHandler

里面内容为异常捕获类的全路径。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
      
        button.apply {
            setOnClickListener {
                GlobalScope.launch {
                    "abc".substring(10)
                }
            }
        }
    }

之后再协程中报个错就能捕获了。

捕获信息:

Unhandled Coroutine Exception: java.lang.StringIndexOutOfBoundsException: length=3; index=10

3.取消与异常

1.取消与异常紧密相关,协程内部使用CancellationException来进行取消,这个异常会被忽略。

2.当子协程被取消时,不会取消它的父协程。


fun testCancelAndException() = runBlocking {
 launch {
     val child = launch {
         try {
            //延迟,挂起
             delay(Long.MAX_VALUE)
         } finally {
             println("Child is cancelled")
         }
     }
     yield()
     println("Cancelling child")
     child.cancelAndJoin()
     yield()
     println("Parent is not cancelled.")
 }
}

打印结果:

Cancelling child
Child is cancelled
Parent is not cancelled.

如果一个协程遇到了CanceIIationException以外的异常,它将使用该异常取消它的父协程。当父协程的所有子协程都结束后,异常才会被父协程处理。

//如果一个协程遇到了取消以外的异常,他将使用该异常取消它的父协程、父协程得把子协程全部关闭才能处理异常(抛出)

fun testCancelAndException2() = runBlocking {
  val handler = CoroutineExceptionHandler() { _, e ->
      println("Caught $e")
  }
    val launch = GlobalScope.launch(handler) {
        launch {
            try {
                //延迟,挂起
                delay(Long.MAX_VALUE)
            } finally {
                withContext(NonCancellable) {
                    println("Children are cancelled, but exception is not handled until all")
                    delay(100)
                    println("The first child finished its non cancellable block ")
                }
            }
        }

        launch {
            delay(100)
            println("Second child h")
        }
    }
    launch.join()
}

打印结果:

Second child h
Children are cancelled, but exception is not handled until all
The first child finished its non cancellable block 
Caught java.lang.ArithmeticException

4.捕获所有子协程的异常

当协程的多个子协程因为异常而失败时,一般情况下取第一个异常进行处理。在第一个异常之后发生的所有其他异常,都将被绑定到第一个异常之上

//查看所有子协程的异常
fun testExceptionAggregation() = runBlocking {
    val handler = CoroutineExceptionHandler() { _, e ->
        println("Caught $e      ${e.suppressed.contentToString()}")
    }
    val launch = GlobalScope.launch(handler) {
        launch {
            try {
                delay(Long.MAX_VALUE)
            } finally {
                throw ArithmeticException();
            }
        }

        launch {
            try {
                delay(Long.MAX_VALUE)
            } finally {
                throw IndexOutOfBoundsException();
            }
        }

        launch {

              throw IOException();

        }
    }
    launch.join()
}

打印结果:

Caught java.io.IOException      [java.lang.ArithmeticException, java.lang.IndexOutOfBoundsException]

你可能感兴趣的:(Kotlin协程,kotlin,android,java)