CoroutineContext是一组用于定义协程行为的元素。它由如下几项构成。
Job:控制协程的生命周期
CoroutineDispatcher:向合适的线程分发任务
CoroutineName:协程的名称,调试的时候很有用
CoroutineExceptionHandler:处理未被捕捉的异常
@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文件夹,进性单元测试才会出现。
对于新创建的协程,它的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) {
}
}
协程构建器有两种形式:自动传播异常(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
//非根协程异常 的async会立即抛出异常
fun testExceptionPropagation2() = runBlocking {
val coroutineScope = CoroutineScope(Job())
val launch = coroutineScope.launch {
async {
throw IndexOutOfBoundsException()
}
}
launch.join()
}
这段代码一运行就会报错,这里async作为子协程发生异常就会向上传,而launch无法处理异常就会报错.
当一个协程由于一个异常而运行失败时,他会传播这个异常并传递给它的父级,接下来父级会进行下面几步操作:
1.取消他自己的自级
2.取消他自己
3.将异常传递给它的父级
@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得作用就是其中一个协程发生异常,其他子协程还会继续执行。
//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()
}
}
使用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()
}
全局异常处理器可以获取到所有协程未处理的未捕获异常,不过它并不能对异常讲行捕获,
虽然不能阻止程序崩溃,全局异常处理器在程序调试和异常上报等场景中仍然有非常大的
用处。
我们需要在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")
}
}
首先创建这个异常捕获类。
随后创建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
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
当协程的多个子协程因为异常而失败时,一般情况下取第一个异常进行处理。在第一个异常之后发生的所有其他异常,都将被绑定到第一个异常之上。
//查看所有子协程的异常
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]