Kotlin-协程上下文CoroutineContext

85 阅读3分钟

前言

协程是并发编程技术,允许在一个线程执行多个任务,不需要创建多个线程。线程是操作系统概念,协程是编程语言概念。协程可以暂停和恢复执行,线程只能被终止。 CoroutineContext定义了协程的执行环境。

CoroutineContext概念

是一个容器,包含了协程上下文信息。

  1. 协程状态:生命周期。Active Completed Canceled
  2. 协程调度策略:决定协程在哪里执行。主线程 后台线程 其他线程池
  3. 协程拦截器:拦截协程的执行流程
  4. 协程异常捕获:处理协程内部发生未捕获的异常
fun main() {
    runBlocking {
        // 协程上下文环境
        println(coroutineContext)
    }
}
输出:
// 默认情况下三部分组成:协程id,协程的Job,协程的dispatcher
[CoroutineId(1), "coroutine#1":BlockingCoroutine{Active}@135606db, BlockingEventLoop@518caac3]

CoroutineContext组成

由多个组件组成,通过context.get<T>()函数获取. 由于重新定义了get操作符,可以通过context[key]获取上下文组件元素。

fun main() = runBlocking {
    val context = coroutineContext//+CoroutineName("张三") //+ Dispatchers.IO
    val dispatcher = context[ContinuationInterceptor]
    val job = context[Job]
    val name = context[CoroutineName]
    println("context:$context")
    println("job:$job")
    println("dispatcher:$dispatcher")
    println("name:$name")
}
输出:
context:[CoroutineId(1), "coroutine#1":BlockingCoroutine{Active}@59d4cd39, BlockingEventLoop@389c4eb1]
job:"coroutine#1":BlockingCoroutine{Active}@59d4cd39
dispatcher:BlockingEventLoop@389c4eb1
name:null
fun main() = runBlocking {
    val context = coroutineContext + CoroutineName("张三") + Dispatchers.IO + SupervisorJob()
    val dispatcher = context[ContinuationInterceptor]
    val job = context[Job]
    val name = context[CoroutineName]
    println("context:$context")
    println("job:$job")
    println("dispatcher:$dispatcher")
    println("name:$name")
}
输出:
context:[CoroutineId(1), CoroutineName(张三), SupervisorJobImpl{Active}@6e15fe2, Dispatchers.IO]
job:SupervisorJobImpl{Active}@6e15fe2
dispatcher:Dispatchers.IO
name:CoroutineName(张三)
  1. Dispatcher:协程的调度策略
  2. Job:协程的状态。表示协程的生命周期
fun main() = runBlocking {
    val context = coroutineContext + SupervisorJob()
    val job = context[Job]
    if (job?.isActive == true) {
        println("协程处于Active状态")
    }
}
输出:
协程处于Active状态

添加拦截器:协程执行前、执行中、执行后操作

class MyContinuationInterceptor:ContinuationInterceptor{
    override val key: CoroutineContext.Key<ContinuationInterceptor>
        get() = ContinuationInterceptor.Key
    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        println("协程开始之前")
        return continuation
    }
}

fun main() {
    runBlocking {
        launch(Dispatchers.IO+MyContinuationInterceptor()) {
            println("协程开始执行")
            delay(1000)
        }
    }
}
输出:
协程开始之前
协程开始执行

CoroutineExceptionHandler

处理协程内部发生的未捕获异常

fun main(){
    println("test main")
    val exceptionHandler = CoroutineExceptionHandler { _, exception ->
        println("Caught an exception: $exception")
    }
    GlobalScope.launch(exceptionHandler) {
        println("test launch1")
        launch {
            println("test launch-> threadName->${Thread.currentThread().name}")
            throw NullPointerException()
        }
    }
    Thread.sleep(2000)
    println("test end")
}
输出:
test main
test launch1
test launch-> threadName->DefaultDispatcher-worker-2 @coroutine#2
Caught an exception: java.lang.NullPointerException
test end

增加了协程内部抛出的异常,会被CoroutineExceptionHandler捕获(上面的协程虽然launch两次但是在同一个域内,可以捕获到异常)。

fun main(){
    println("test main")
    GlobalScope.launch(CoroutineExceptionHandler { _, throwable ->
        println("MainActivity_${throwable.message.toString()}")
    }) {
        GlobalScope.launch {
            println("MainActivity_ launch-> threadName->${Thread.currentThread().name}")
            // 异常不被CoroutineExceptionHandler捕获
            throw NullPointerException()
        }
    }
    Thread.sleep(2000)
    println("test end")
}
输出:
test main
MainActivity_ launch-> threadName->DefaultDispatcher-worker-2 @coroutine#2
Exception in thread "DefaultDispatcher-worker-2 @coroutine#2" java.lang.NullPointerException.....
test end

CoroutineExceptionHandler只能处理当前域内开启的子线程或当前协程抛出的异常。所以上面父域无法捕获子域额异常的(在不同的域内,所以无法捕获到异常)。

fun main() {
    println("test main")
    val scope = CoroutineScope(Job())
    scope.launch {
        launch(CoroutineExceptionHandler { _, _ -> println("test catch") }) {
            delay(10)
            // 异常不被捕获
            throw RuntimeException()
        }
    }
    Thread.sleep(2000)
    println("test end")
}
输出:
test main
Exception in thread ....
test end

当子协程发生异常时,会优先将异常委托给父协程区处理,直到根协程作用域或顶级协程。因此永远不会使用我们子协程(CoroutineExceptionHandler(SupervisorJob除外))。

import kotlinx.coroutines.*
fun main() {
    // 创建CoroutineExceptionHandler
    val exceptionHandler = CoroutineExceptionHandler { _, exception ->
        println("Caught an exception: $exception")
    }
    runBlocking {
    	val context = coroutineContext + exceptionHandler
        val job = GlobalScope.launch(context) {
            println("Coroutine is doing some work")
            delay(1000)
            // 异常无法被捕获;当子协程发生异常时,会优先将异常委托给父协程区处理
            throw CustomException("Something went wrong!")
        }

        // 等待协程执行结束
        job.join()
    }
}

// 自定义异常类
class CustomException(message: String) : Exception(message)

上面无法捕获到异常。

总结

CoroutineContext来控制协程的执行,合理选择调度器,细致管理,以及异常处理。